feat(anim): 实现2D模型动画系统核心类

- 添加AnimationClip类用于管理动画剪辑和关键帧
- 添加AnimationLayer类支持动画层和混合模式
- 实现动画曲线采样和插值算法
- 支持事件标记和动画状态控制
- 添加参数覆盖和权重混合功能
- 实现动画轨道和关键帧管理- 添加多种插值类型支持(线性、步进、平滑、缓入缓出)
- 实现动画事件系统和监听器模式
- 支持动画剪辑的深拷贝和合并功能
- 添加AnimationParameter类用于动画参数管理
This commit is contained in:
tzdwindows 7
2025-10-08 11:08:57 +08:00
parent 1e0aa62ca8
commit 3cf7f5883c
23 changed files with 8448 additions and 1 deletions

View File

@@ -82,11 +82,14 @@ dependencies {
implementation 'com.1stleg:jnativehook:2.1.0'
implementation 'org.json:json:20230618'
implementation 'org.lwjgl:lwjgl:3.3.1'
implementation 'org.lwjgl:lwjgl-stb:3.3.3'
implementation 'org.lwjgl:lwjgl-glfw:3.3.1'
implementation 'org.lwjgl:lwjgl-opengl:3.3.1'
implementation 'org.lwjgl:lwjgl:3.3.1:natives-windows'
implementation 'org.lwjgl:lwjgl-glfw:3.3.1:natives-windows'
implementation 'org.lwjgl:lwjgl-opengl:3.3.1:natives-windows'
implementation 'com.badlogicgames.gdx:gdx:1.12.1'
implementation 'org.joml:joml:1.10.7'
implementation 'org.bytedeco:javacv-platform:1.5.7'
implementation 'org.bytedeco:javacpp-platform:1.5.7'
implementation 'com.madgag:animated-gif-lib:1.4'

View File

@@ -1287,7 +1287,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
applyLanguageSettings(settingsManager);
// 5. 应用CUDA设置
applyCUDASettings(settingsManager);
//applyCUDASettings(settingsManager);
logger.info("所有设置配置已成功应用");

View File

@@ -0,0 +1,8 @@
package com.chuangzhou.vivid2D;
public class Main {
public static void main(String[] args) {
}
}

View File

@@ -0,0 +1,527 @@
package com.chuangzhou.vivid2D.render;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.Texture;
import org.joml.Matrix3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 重构后的 ModelRender更模块化、健壮的渲染子系统
*/
public final class ModelRender {
private ModelRender() { /* no instances */ }
// ================== 全局状态 ==================
private static boolean initialized = false;
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 Map<String, ShaderProgram> shaderMap = new HashMap<>();
private static ShaderProgram defaultProgram = null;
private static final Map<Mesh2D, MeshGLResources> meshResources = new HashMap<>();
private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0);
// 默认白色纹理
private static int defaultTextureId = 0;
// ================== 内部类ShaderProgram ==================
private static class ShaderProgram {
final int programId;
final Map<String, Integer> uniformCache = new HashMap<>();
ShaderProgram(int programId) {
this.programId = programId;
}
void use() {
GL20.glUseProgram(programId);
}
void stop() {
GL20.glUseProgram(0);
}
int getUniformLocation(String name) {
return uniformCache.computeIfAbsent(name, k -> {
int loc = GL20.glGetUniformLocation(programId, k);
if (loc == -1) {
// debug 时可以打开
// System.err.println("Warning: uniform not found: " + k);
}
return loc;
});
}
void delete() {
if (GL20.glIsProgram(programId)) GL20.glDeleteProgram(programId);
}
}
// ================== 内部类MeshGLResources ==================
private static class MeshGLResources {
int vao = 0;
int vbo = 0;
int ebo = 0;
int vertexCount = 0;
boolean initialized = false;
void dispose() {
if (ebo != 0) { GL15.glDeleteBuffers(ebo); ebo = 0; }
if (vbo != 0) { GL15.glDeleteBuffers(vbo); vbo = 0; }
if (vao != 0) { GL30.glDeleteVertexArrays(vao); vao = 0; }
initialized = false;
}
}
// ================== 着色器源 ==================
private static final String VERTEX_SHADER_SRC =
"#version 330 core\n" +
"layout(location = 0) in vec2 aPosition;\n" +
"layout(location = 1) in vec2 aTexCoord;\n" +
"out vec2 vTexCoord;\n" +
"uniform mat3 uModelMatrix;\n" +
"uniform mat3 uViewMatrix;\n" +
"uniform mat3 uProjectionMatrix;\n" +
"void main() {\n" +
" vec3 p = uProjectionMatrix * uViewMatrix * uModelMatrix * vec3(aPosition, 1.0);\n" +
" gl_Position = vec4(p.xy, 0.0, 1.0);\n" +
" vTexCoord = aTexCoord;\n" +
"}";
private static final String FRAGMENT_SHADER_SRC =
"#version 330 core\n" +
"in vec2 vTexCoord;\n" +
"out vec4 FragColor;\n" +
"uniform sampler2D uTexture;\n" +
"uniform vec4 uColor;\n" +
"uniform float uOpacity;\n" +
"uniform int uBlendMode;\n" +
"void main() {\n" +
" vec4 tex = texture(uTexture, vTexCoord);\n" +
" vec4 finalColor = tex * uColor;\n" +
" if (uBlendMode == 1) finalColor.rgb = tex.rgb + uColor.rgb;\n" +
" else if (uBlendMode == 2) finalColor.rgb = tex.rgb * uColor.rgb;\n" +
" else if (uBlendMode == 3) finalColor.rgb = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);\n" +
" finalColor.a = tex.a * uOpacity;\n" +
" if (finalColor.a <= 0.001) discard;\n" +
" FragColor = finalColor;\n" +
"}";
// ================== 初始化 / 清理 ==================
public static synchronized void initialize() {
if (initialized) return;
System.out.println("Initializing ModelRender...");
// 需要在外部创建 OpenGL 上下文并调用 GL.createCapabilities()
logGLInfo();
// 初始 GL 状态
setupGLState();
// 创建默认 shader
try {
compileDefaultShader();
} catch (RuntimeException ex) {
System.err.println("Failed to compile default shader: " + ex.getMessage());
throw ex;
}
// 创建默认纹理
createDefaultTexture();
// 初始化视口
GL11.glViewport(0, 0, viewportWidth, viewportHeight);
initialized = true;
System.out.println("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));
}
private static void setupGLState() {
GL11.glClearColor(CLEAR_COLOR.x, CLEAR_COLOR.y, CLEAR_COLOR.z, CLEAR_COLOR.w);
if (enableBlending) {
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
} else {
GL11.glDisable(GL11.GL_BLEND);
}
if (enableDepthTest) {
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glDepthFunc(GL11.GL_LEQUAL);
} else {
GL11.glDisable(GL11.GL_DEPTH_TEST);
}
GL11.glDisable(GL11.GL_CULL_FACE);
checkGLError("setupGLState");
}
private static void compileDefaultShader() {
int vs = compileShader(GL20.GL_VERTEX_SHADER, VERTEX_SHADER_SRC);
int fs = compileShader(GL20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SRC);
int prog = linkProgram(vs, fs);
ShaderProgram sp = new ShaderProgram(prog);
shaderMap.put("default", sp);
defaultProgram = sp;
// 设置一些默认 uniform需要先 use
sp.use();
setUniformIntInternal(sp, "uTexture", 0);
setUniformFloatInternal(sp, "uOpacity", 1.0f);
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
setUniformIntInternal(sp, "uBlendMode", 0);
sp.stop();
}
private static int compileShader(int type, String src) {
int shader = GL20.glCreateShader(type);
GL20.glShaderSource(shader, src);
GL20.glCompileShader(shader);
int status = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
if (status == GL11.GL_FALSE) {
String log = GL20.glGetShaderInfoLog(shader);
GL20.glDeleteShader(shader);
throw new RuntimeException("Shader compilation failed: " + log);
}
return shader;
}
private static int linkProgram(int vs, int fs) {
int prog = GL20.glCreateProgram();
GL20.glAttachShader(prog, vs);
GL20.glAttachShader(prog, fs);
GL20.glLinkProgram(prog);
int status = GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS);
if (status == GL11.GL_FALSE) {
String log = GL20.glGetProgramInfoLog(prog);
GL20.glDeleteProgram(prog);
throw new RuntimeException("Program link failed: " + log);
}
// shaders can be deleted after linking
GL20.glDetachShader(prog, vs);
GL20.glDetachShader(prog, fs);
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
return prog;
}
private static void createDefaultTexture() {
// 使用 GL11.glGenTextures() 获取单个 id更直观避免 IntBuffer 问题)
defaultTextureId = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId);
ByteBuffer white = MemoryUtil.memAlloc(4);
white.put((byte)255).put((byte)255).put((byte)255).put((byte)255).flip();
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 1, 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, white);
MemoryUtil.memFree(white);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
checkGLError("createDefaultTexture");
}
public static synchronized void cleanup() {
if (!initialized) return;
System.out.println("Cleaning up ModelRender...");
// mesh resources
for (MeshGLResources r : meshResources.values()) r.dispose();
meshResources.clear();
// shaders
for (ShaderProgram sp : shaderMap.values()) sp.delete();
shaderMap.clear();
defaultProgram = null;
// textures
if (defaultTextureId != 0) {
GL11.glDeleteTextures(defaultTextureId);
defaultTextureId = 0;
}
initialized = false;
System.out.println("ModelRender cleaned up");
}
// ================== 渲染流程 ==================
public static void render(float deltaTime, Model2D model) {
if (!initialized) throw new IllegalStateException("ModelRender not initialized");
if (model == null) return;
// 更新模型(确保 worldTransform 已经被计算)
model.update(deltaTime);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0));
// 使用默认 shader保持绑定直到完成渲染
defaultProgram.use();
// 设置投影与视图3x3 正交投影用于 2D
Matrix3f proj = buildOrthoProjection(viewportWidth, viewportHeight);
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
setUniformMatrix3(defaultProgram, "uViewMatrix", new Matrix3f().identity());
// 递归渲染所有根部件(使用 3x3 矩阵)
Matrix3f identity = new Matrix3f().identity();
for (ModelPart p : model.getParts()) {
if (p.getParent() != null) continue;
renderPartRecursive(p, identity);
}
defaultProgram.stop();
checkGLError("render");
}
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
// 使用 part 内置的局部矩阵localTransform并与 parentMat 相乘得到 world 矩阵
Matrix3f local = part.getLocalTransform(); // 返回 copy
Matrix3f world = new Matrix3f(parentMat).mul(local); // world = parent * local
// 把 world 矩阵传入 shader使用 3x3
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
// 设置部件相关 uniformopacity / blend / color
setPartUniforms(defaultProgram, part);
// 绘制该部件的所有网格(使用 ModelRender 的 renderMesh
for (Mesh2D mesh : part.getMeshes()) {
renderMesh(mesh);
}
// 递归绘制子节点(传入当前 world 矩阵)
for (ModelPart child : part.getChildren()) {
renderPartRecursive(child, world);
}
}
private static void renderMesh(Mesh2D mesh) {
// 确保 mesh 的 GL 资源已上传ModelRender 管理 upload
MeshGLResources res = meshResources.computeIfAbsent(mesh, k -> new MeshGLResources());
if (!res.initialized) uploadMeshData(mesh, res);
// 绑定纹理到单元0我们使用 0 固定)
Texture tex = mesh.getTexture();
int texId = (tex != null && !tex.isDisposed()) ? tex.getTextureId() : defaultTextureId;
// active unit & bind — 确保 shader 已被 use()(调用者保证)
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
// 将 sampler 设为 0内部函数保证 program 绑定)
setUniformIntInternal(defaultProgram, "uTexture", 0);
// 绑定 VAO 并绘制
GL30.glBindVertexArray(res.vao);
int drawMode = getGLDrawMode(mesh.getDrawMode());
if (mesh.getIndices().length > 0 &&
(drawMode == GL11.GL_TRIANGLES || drawMode == GL11.GL_TRIANGLE_STRIP || drawMode == GL11.GL_TRIANGLE_FAN)) {
GL11.glDrawElements(drawMode, mesh.getIndices().length, GL11.GL_UNSIGNED_INT, 0);
} else {
GL11.glDrawArrays(drawMode, 0, res.vertexCount);
}
GL30.glBindVertexArray(0);
// 解绑纹理(避免污染后续 state
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
checkGLError("renderMesh");
}
private static int getGLDrawMode(int meshDrawMode) {
switch (meshDrawMode) {
case Mesh2D.POINTS: return GL11.GL_POINTS;
case Mesh2D.LINES: return GL11.GL_LINES;
case Mesh2D.LINE_STRIP: return GL11.GL_LINE_STRIP;
case Mesh2D.TRIANGLES: return GL11.GL_TRIANGLES;
case Mesh2D.TRIANGLE_STRIP: return GL11.GL_TRIANGLE_STRIP;
case Mesh2D.TRIANGLE_FAN: return GL11.GL_TRIANGLE_FAN;
default: return GL11.GL_TRIANGLES;
}
}
// ================== 上传数据 ==================
private static void uploadMeshData(Mesh2D mesh, MeshGLResources res) {
System.out.println("Uploading mesh data: " + mesh.getName());
res.vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(res.vao);
res.vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, res.vbo);
float[] verts = mesh.getVertices();
float[] uvs = mesh.getUVs();
int vertexCount = mesh.getVertexCount();
if (verts == null || verts.length == 0) throw new IllegalStateException("Mesh has no vertices: " + mesh.getName());
FloatBuffer inter = MemoryUtil.memAllocFloat(vertexCount * 4);
for (int i = 0; i < vertexCount; i++) {
inter.put(verts[i*2]);
inter.put(verts[i*2+1]);
inter.put(uvs[i*2]);
inter.put(uvs[i*2+1]);
}
inter.flip();
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, inter, GL15.GL_STATIC_DRAW);
MemoryUtil.memFree(inter);
// 设置 attribute位置 / uvlayout 已在 shader 中固定
int stride = 4 * Float.BYTES;
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, stride, 0);
GL20.glEnableVertexAttribArray(1);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, stride, 2 * Float.BYTES);
int[] indices = mesh.getIndices();
if (indices != null && indices.length > 0) {
res.ebo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, res.ebo);
IntBuffer ib = MemoryUtil.memAllocInt(indices.length);
ib.put(indices).flip();
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ib, GL15.GL_STATIC_DRAW);
MemoryUtil.memFree(ib);
res.vertexCount = indices.length; // drawElements 使用 count
} else {
res.vertexCount = vertexCount;
}
// 不解绑 ELEMENT_ARRAY_BUFFER它属于 VAO解绑 ARRAY_BUFFER
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
res.initialized = true;
checkGLError("uploadMeshData");
System.out.println("Uploaded mesh: " + mesh.getName() + " (v=" + vertexCount + ")");
}
// ================== uniform 设置辅助(内部使用,确保 program 已绑定) ==================
private static void setUniformIntInternal(ShaderProgram sp, String name, int value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) GL20.glUniform1i(loc, value);
}
private static void setUniformFloatInternal(ShaderProgram sp, String name, float value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) GL20.glUniform1f(loc, value);
}
private static void setUniformVec4Internal(ShaderProgram sp, String name, Vector4f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) GL20.glUniform4f(loc, vec.x, vec.y, vec.z, vec.w);
}
private static void setUniformMatrix3(ShaderProgram sp, String name, Matrix3f m) {
int loc = sp.getUniformLocation(name);
if (loc == -1) return;
FloatBuffer fb = MemoryUtil.memAllocFloat(9);
try {
m.get(fb);
GL20.glUniformMatrix3fv(loc, false, fb);
} finally {
MemoryUtil.memFree(fb);
}
}
// 外部可用的统一设置(会自动切换到默认程序)
private static void setUniformInt(String name, int value) {
defaultProgram.use();
setUniformIntInternal(defaultProgram, name, value);
defaultProgram.stop();
}
private static void setUniformFloat(String name, float value) {
defaultProgram.use();
setUniformFloatInternal(defaultProgram, name, value);
defaultProgram.stop();
}
private static void setUniformVec4(String name, Vector4f v) {
defaultProgram.use();
setUniformVec4Internal(defaultProgram, name, v);
defaultProgram.stop();
}
// ================== 部件属性 ==================
private static void setPartUniforms(ShaderProgram sp, ModelPart part) {
setUniformFloatInternal(sp, "uOpacity", part.getOpacity());
int blend = 0;
switch (part.getBlendMode()) {
case ADDITIVE: blend = 1; break;
case MULTIPLY: blend = 2; break;
case SCREEN: blend = 3; break;
case NORMAL: default: blend = 0;
}
setUniformIntInternal(sp, "uBlendMode", blend);
// 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
}
// ================== 工具 ==================
private static Matrix3f buildOrthoProjection(int width, int height) {
Matrix3f m = new Matrix3f();
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
return m;
}
public static void setViewport(int width, int height) {
viewportWidth = Math.max(1, width);
viewportHeight = Math.max(1, height);
GL11.glViewport(0, 0, viewportWidth, viewportHeight);
}
public static void setClearColor(float r, float g, float b, float a) {
GL11.glClearColor(r,g,b,a);
}
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));
}
}
private static String getGLErrorString(int err) {
switch (err) {
case GL11.GL_INVALID_ENUM: return "GL_INVALID_ENUM";
case GL11.GL_INVALID_VALUE: return "GL_INVALID_VALUE";
case GL11.GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
case GL11.GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
default: return "Unknown(0x" + Integer.toHexString(err) + ")";
}
}
// ================== 辅助:外部获取状态 ==================
public static boolean isInitialized() { return initialized; }
public static int getLoadedMeshCount() { return meshResources.size(); }
}

View File

@@ -0,0 +1,58 @@
package com.chuangzhou.vivid2D.render.model;
public class AnimationParameter {
private String id;
private float value;
private float defaultValue;
private float minValue;
private float maxValue;
private boolean changed = false;
public AnimationParameter(String id, float min, float max, float defaultValue) {
this.id = id;
this.minValue = min;
this.maxValue = max;
this.defaultValue = defaultValue;
this.value = defaultValue;
}
public void setValue(float value) {
float clamped = Math.max(minValue, Math.min(maxValue, value));
if (this.value != clamped) {
this.value = clamped;
this.changed = true;
}
}
public boolean hasChanged() { return changed; }
public void markClean() { this.changed = false; }
public float getValue() { return value; }
public String getId() { return id; }
public float getMinValue() { return minValue; }
public float getMaxValue() { return maxValue; }
public float getDefaultValue() { return defaultValue; }
public void reset() {
this.value = defaultValue;
this.changed = false;
}
/**
* 获取归一化值 [0, 1]
*/
public float getNormalizedValue() {
return (value - minValue) / (maxValue - minValue);
}
/**
* 设置归一化值
*/
public void setNormalizedValue(float normalized) {
this.value = minValue + normalized * (maxValue - minValue);
}
}

View File

@@ -0,0 +1,347 @@
package com.chuangzhou.vivid2D.render.model;
import com.chuangzhou.vivid2D.render.model.util.*;
import org.joml.Matrix3f;
import java.util.*;
/**
* 2D模型核心数据结构
* 支持层级变换、网格变形、参数驱动动画等功能
*
* 例子
* // 创建模型
* Model2D model = new Model2D("character");
* model.setVersion("1.0.0");
*
* // 添加部件
* ModelPart head = model.createPart("head");
* ModelPart body = model.createPart("body");
* ModelPart leftArm = model.createPart("left_arm");
*
* // 建立层级关系
* body.addChild(head);
* body.addChild(leftArm);
*
* // 创建网格
* Mesh2D headMesh = Mesh2D.createQuad("head_mesh", 50, 50);
* Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 40, 80);
* head.addMesh(headMesh);
* body.addMesh(bodyMesh);
*
* // 添加参数
* AnimationParameter smileParam = model.createParameter("smile", 0, 1, 0);
* AnimationParameter blinkParam = model.createParameter("blink", 0, 1, 0);
*
* // 保存模型
* model.saveToFile("character.model");
*
* // 加载模型
* Model2D loadedModel = Model2D.loadFromFile("character.model");
*
* // 使用加载的模型
* loadedModel.setParameterValue("smile", 0.8f);
* loadedModel.update(0.016f); // 更新模型状态
*
* @author tzdwindows 7
*/
public class Model2D {
// ==================== 基础属性 ====================
private String name;
private String version = "1.0.0";
private UUID uuid;
private ModelMetadata metadata;
// ==================== 层级结构 ====================
private final List<ModelPart> parts;
private final Map<String, ModelPart> partMap; // 快速查找
private ModelPart rootPart;
// ==================== 网格系统 ====================
private final List<Mesh2D> meshes;
private final Map<String, Texture> textures; // 纹理映射
// ==================== 动画系统 ====================
private final Map<String, AnimationParameter> parameters;
private final List<AnimationLayer> animationLayers;
private final PhysicsSystem physics;
// ==================== 渲染状态 ====================
private transient ModelPose currentPose;
private transient boolean needsUpdate = true;
private transient BoundingBox bounds;
// ==================== 构造器 ====================
public Model2D() {
this.uuid = UUID.randomUUID();
this.parts = new ArrayList<>();
this.partMap = new HashMap<>();
this.meshes = new ArrayList<>();
this.textures = new HashMap<>();
this.parameters = new LinkedHashMap<>(); // 保持插入顺序
this.animationLayers = new ArrayList<>();
this.physics = new PhysicsSystem();
this.currentPose = new ModelPose();
this.metadata = new ModelMetadata();
}
public Model2D(String name) {
this();
this.name = name;
}
// ==================== 部件管理 ====================
public ModelPart createPart(String name) {
ModelPart part = new ModelPart(name);
addPart(part);
return part;
}
public void addPart(ModelPart part) {
if (partMap.containsKey(part.getName())) {
throw new IllegalArgumentException("Part already exists: " + part.getName());
}
parts.add(part);
partMap.put(part.getName(), part);
// 设置根部件(第一个添加的部件)
if (rootPart == null) {
rootPart = part;
}
}
public ModelPart getPart(String name) {
return partMap.get(name);
}
public List<ModelPart> getParts() {
return Collections.unmodifiableList(parts);
}
// ==================== 参数管理 ====================
public AnimationParameter createParameter(String id, float min, float max, float defaultValue) {
AnimationParameter param = new AnimationParameter(id, min, max, defaultValue);
parameters.put(id, param);
return param;
}
public AnimationParameter getParameter(String id) {
return parameters.get(id);
}
public void addParameter(AnimationParameter param) {
parameters.put(param.getId(), param);
}
public void setParameterValue(String paramId, float value) {
AnimationParameter param = parameters.get(paramId);
if (param != null) {
param.setValue(value);
markNeedsUpdate();
}
}
public float getParameterValue(String paramId) {
AnimationParameter param = parameters.get(paramId);
return param != null ? param.getValue() : 0.0f;
}
// ==================== 网格管理 ====================
public Mesh2D createMesh(String name, float[] vertices, float[] uvs, int[] indices) {
Mesh2D mesh = new Mesh2D(name, vertices, uvs, indices);
meshes.add(mesh);
return mesh;
}
public void addMesh(Mesh2D mesh) {
meshes.add(mesh);
}
public Mesh2D getMesh(String name) {
for (Mesh2D mesh : meshes) {
if (mesh.getName().equals(name)) {
return mesh;
}
}
return null;
}
// ==================== 纹理管理 ====================
public void addTexture(Texture texture) {
textures.put(texture.getName(), texture);
}
public Texture getTexture(String name) {
return textures.get(name);
}
public Map<String, Texture> getTextures() {
return Collections.unmodifiableMap(textures);
}
// ==================== 动画层管理 ====================
public AnimationLayer createAnimationLayer(String name) {
AnimationLayer layer = new AnimationLayer(name);
animationLayers.add(layer);
return layer;
}
// ==================== 更新系统 ====================
public void update(float deltaTime) {
if (!needsUpdate && !physics.hasActivePhysics()) {
return;
}
// 更新物理系统
physics.update(deltaTime, this);
// 更新所有参数驱动的变形
updateParameterDeformations();
// 更新层级变换
updateHierarchyTransforms();
// 更新包围盒
updateBoundingBox();
needsUpdate = false;
}
private void updateParameterDeformations() {
for (AnimationParameter param : parameters.values()) {
if (param.hasChanged()) {
applyParameterDeformations(param);
param.markClean();
}
}
}
private void applyParameterDeformations(AnimationParameter param) {
// 这里将实现参数到具体变形的映射
// 例如:参数"face_smile" -> 应用到嘴部网格的变形
for (ModelPart part : parts) {
part.applyParameter(param);
}
}
private void updateHierarchyTransforms() {
if (rootPart != null) {
Matrix3f matrix = new Matrix3f();
matrix.identity();
rootPart.updateWorldTransform(matrix, true);
}
}
private void updateBoundingBox() {
if (bounds == null) {
bounds = new BoundingBox();
}
bounds.reset();
for (ModelPart part : parts) {
bounds.expand(part.getWorldBounds());
}
}
// ==================== 工具方法 ====================
public void markNeedsUpdate() {
this.needsUpdate = true;
}
public boolean isVisible() {
return rootPart != null && rootPart.isVisible();
}
public void setVisible(boolean visible) {
if (rootPart != null) {
rootPart.setVisible(visible);
}
}
// ==================== 序列化支持 ====================
public ModelData serialize() {
return new ModelData(this);
}
public static Model2D deserialize(ModelData data) {
return data.deserializeToModel();
}
/**
* 保存模型到文件
*/
public void saveToFile(String filePath) {
try {
ModelData data = serialize();
data.saveToFile(filePath);
} catch (Exception e) {
throw new RuntimeException("Failed to save model to: " + filePath, e);
}
}
/**
* 从文件加载模型
*/
public static Model2D loadFromFile(String filePath) {
try {
ModelData data = ModelData.loadFromFile(filePath);
return deserialize(data);
} catch (Exception e) {
throw new RuntimeException("Failed to load model from: " + filePath, e);
}
}
/**
* 保存模型到压缩文件
*/
public void saveToCompressedFile(String filePath) {
try {
ModelData data = serialize();
data.saveToCompressedFile(filePath);
} catch (Exception e) {
throw new RuntimeException("Failed to save compressed model to: " + filePath, e);
}
}
/**
* 从压缩文件加载模型
*/
public static Model2D loadFromCompressedFile(String filePath) {
try {
ModelData data = ModelData.loadFromCompressedFile(filePath);
return deserialize(data);
} catch (Exception e) {
throw new RuntimeException("Failed to load compressed model from: " + filePath, e);
}
}
// ==================== Getter/Setter ====================
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public UUID getUuid() { return uuid; }
public void setUuid(UUID uuid) { this.uuid = uuid; }
public ModelMetadata getMetadata() { return metadata; }
public void setMetadata(ModelMetadata metadata) { this.metadata = metadata; }
public ModelPart getRootPart() { return rootPart; }
public void setRootPart(ModelPart rootPart) { this.rootPart = rootPart; }
public List<Mesh2D> getMeshes() { return Collections.unmodifiableList(meshes); }
public Map<String, AnimationParameter> getParameters() {
return Collections.unmodifiableMap(parameters);
}
public List<AnimationLayer> getAnimationLayers() {
return Collections.unmodifiableList(animationLayers);
}
public PhysicsSystem getPhysics() { return physics; }
public ModelPose getCurrentPose() { return currentPose; }
public BoundingBox getBounds() { return bounds; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
}

View File

@@ -0,0 +1,766 @@
package com.chuangzhou.vivid2D.render.model;
import com.chuangzhou.vivid2D.render.model.util.*;
import org.joml.Vector2f;
import java.io.*;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* 模型数据类,用于模型的序列化、反序列化和数据交换
* 支持二进制和JSON格式的模型数据存储
*
* @author tzdwindows 7
*/
public class ModelData implements Serializable {
private static final long serialVersionUID = 1L;
// ==================== 模型元数据 ====================
private String name;
private String version;
private UUID uuid;
private String author;
private String description;
private long creationTime;
private long lastModifiedTime;
// ==================== 模型结构数据 ====================
private List<PartData> parts;
private List<MeshData> meshes;
private List<TextureData> textures;
private List<ParameterData> parameters;
private List<AnimationData> animations;
// ==================== 模型设置 ====================
private Vector2f pivotPoint;
private float unitsPerMeter;
private Map<String, String> userData;
// ==================== 构造器 ====================
public ModelData() {
this("unnamed");
}
public ModelData(String name) {
this.name = name;
this.version = "1.0.0";
this.uuid = UUID.randomUUID();
this.creationTime = System.currentTimeMillis();
this.lastModifiedTime = creationTime;
this.parts = new ArrayList<>();
this.meshes = new ArrayList<>();
this.textures = new ArrayList<>();
this.parameters = new ArrayList<>();
this.animations = new ArrayList<>();
this.pivotPoint = new Vector2f();
this.unitsPerMeter = 100.0f; // 默认100单位/米
this.userData = new HashMap<>();
}
public ModelData(Model2D model) {
this(model.getName());
serializeFromModel(model);
}
// ==================== 序列化方法 ====================
/**
* 从Model2D对象序列化数据
*/
public void serializeFromModel(Model2D model) {
if (model == null) {
throw new IllegalArgumentException("Model cannot be null");
}
this.name = model.getName();
this.version = model.getVersion();
this.uuid = model.getUuid();
// 序列化元数据
if (model.getMetadata() != null) {
this.author = model.getMetadata().getAuthor();
this.description = model.getMetadata().getDescription();
}
// 序列化部件
serializeParts(model);
// 序列化网格
serializeMeshes(model);
// 序列化纹理
serializeTextures(model);
// 序列化参数
serializeParameters(model);
lastModifiedTime = System.currentTimeMillis();
}
private void serializeParts(Model2D model) {
parts.clear();
for (ModelPart part : model.getParts()) {
parts.add(new PartData(part));
}
}
private void serializeMeshes(Model2D model) {
meshes.clear();
for (Mesh2D mesh : model.getMeshes()) {
meshes.add(new MeshData(mesh));
}
}
private void serializeTextures(Model2D model) {
textures.clear();
for (Texture texture : model.getTextures().values()) {
textures.add(new TextureData(texture));
}
}
private void serializeParameters(Model2D model) {
parameters.clear();
for (AnimationParameter param : model.getParameters().values()) {
parameters.add(new ParameterData(param));
}
}
/**
* 反序列化到Model2D对象
*/
public Model2D deserializeToModel() {
Model2D model = new Model2D(name);
model.setVersion(version);
model.setUuid(uuid);
// 设置元数据
ModelMetadata metadata = new ModelMetadata();
metadata.setAuthor(author);
metadata.setDescription(description);
model.setMetadata(metadata);
// 先创建所有纹理
Map<String, Texture> textureMap = deserializeTextures();
// 然后创建所有网格(依赖纹理)
Map<String, Mesh2D> meshMap = deserializeMeshes(textureMap);
// 然后创建部件(依赖网格)
deserializeParts(model, meshMap);
// 最后创建参数
deserializeParameters(model);
return model;
}
private Map<String, Texture> deserializeTextures() {
Map<String, Texture> textureMap = new HashMap<>();
for (TextureData textureData : textures) {
Texture texture = textureData.toTexture();
textureMap.put(texture.getName(), texture);
}
return textureMap;
}
private Map<String, Mesh2D> deserializeMeshes(Map<String, Texture> textureMap) {
Map<String, Mesh2D> meshMap = new HashMap<>();
for (MeshData meshData : meshes) {
Mesh2D mesh = meshData.toMesh2D();
// 设置纹理
if (meshData.textureName != null) {
Texture texture = textureMap.get(meshData.textureName);
if (texture != null) {
mesh.setTexture(texture);
}
}
meshMap.put(mesh.getName(), mesh);
}
return meshMap;
}
private void deserializeParts(Model2D model, Map<String, Mesh2D> meshMap) {
// 先创建所有部件
Map<String, ModelPart> partMap = new HashMap<>();
for (PartData partData : parts) {
ModelPart part = partData.toModelPart(meshMap);
partMap.put(part.getName(), part);
model.addPart(part);
}
// 然后建立父子关系
for (PartData partData : parts) {
if (partData.parentName != null && !partData.parentName.isEmpty()) {
ModelPart child = partMap.get(partData.name);
ModelPart parent = partMap.get(partData.parentName);
if (parent != null && child != null) {
parent.addChild(child);
}
}
}
// 设置根部件
for (PartData partData : parts) {
if (partData.parentName == null || partData.parentName.isEmpty()) {
model.setRootPart(partMap.get(partData.name));
break;
}
}
}
private void deserializeParameters(Model2D model) {
for (ParameterData paramData : parameters) {
AnimationParameter param = paramData.toAnimationParameter();
model.addParameter(param);
}
}
// ==================== 文件操作 ====================
/**
* 保存到文件
*/
public void saveToFile(String filePath) throws IOException {
saveToFile(new File(filePath));
}
public void saveToFile(File file) throws IOException {
// 确保目录存在
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
oos.writeObject(this);
}
}
/**
* 保存为压缩文件
*/
public void saveToCompressedFile(String filePath) throws IOException {
saveToCompressedFile(new File(filePath));
}
public void saveToCompressedFile(File file) throws IOException {
// 确保目录存在
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
try (ObjectOutputStream oos = new ObjectOutputStream(
new GZIPOutputStream(new FileOutputStream(file)))) {
oos.writeObject(this);
}
}
/**
* 从文件加载
*/
public static ModelData loadFromFile(String filePath) throws IOException, ClassNotFoundException {
return loadFromFile(new File(filePath));
}
public static ModelData loadFromFile(File file) throws IOException, ClassNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException("Model file not found: " + file.getAbsolutePath());
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
return (ModelData) ois.readObject();
}
}
/**
* 从压缩文件加载
*/
public static ModelData loadFromCompressedFile(String filePath) throws IOException, ClassNotFoundException {
return loadFromCompressedFile(new File(filePath));
}
public static ModelData loadFromCompressedFile(File file) throws IOException, ClassNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException("Compressed model file not found: " + file.getAbsolutePath());
}
try (ObjectInputStream ois = new ObjectInputStream(
new GZIPInputStream(new FileInputStream(file)))) {
return (ModelData) ois.readObject();
}
}
// ==================== 数据验证 ====================
/**
* 验证模型数据的完整性
*/
public boolean validate() {
if (name == null || name.trim().isEmpty()) {
return false;
}
if (uuid == null) {
return false;
}
// 检查所有部件引用有效的网格
for (PartData part : parts) {
for (String meshName : part.meshNames) {
if (!meshExists(meshName)) {
return false;
}
}
}
return true;
}
private boolean meshExists(String meshName) {
return meshes.stream().anyMatch(mesh -> mesh.name.equals(meshName));
}
/**
* 获取验证错误信息
*/
public List<String> getValidationErrors() {
List<String> errors = new ArrayList<>();
if (name == null || name.trim().isEmpty()) {
errors.add("Model name is required");
}
if (uuid == null) {
errors.add("Model UUID is required");
}
// 检查网格引用
for (PartData part : parts) {
for (String meshName : part.meshNames) {
if (!meshExists(meshName)) {
errors.add("Part '" + part.name + "' references non-existent mesh: " + meshName);
}
}
}
return errors;
}
// ==================== 工具方法 ====================
/**
* 创建深拷贝
*/
public ModelData copy() {
ModelData copy = new ModelData(name + "_copy");
copy.version = this.version;
copy.uuid = UUID.randomUUID();
copy.author = this.author;
copy.description = this.description;
copy.creationTime = System.currentTimeMillis();
copy.lastModifiedTime = copy.creationTime;
// 深拷贝集合
for (PartData part : this.parts) {
copy.parts.add(part.copy());
}
for (MeshData mesh : this.meshes) {
copy.meshes.add(mesh.copy());
}
for (TextureData texture : this.textures) {
copy.textures.add(texture.copy());
}
for (ParameterData param : this.parameters) {
copy.parameters.add(param.copy());
}
for (AnimationData anim : this.animations) {
copy.animations.add(anim.copy());
}
copy.pivotPoint = new Vector2f(this.pivotPoint);
copy.unitsPerMeter = this.unitsPerMeter;
copy.userData = new HashMap<>(this.userData);
return copy;
}
/**
* 合并另一个模型数据
*/
public void merge(ModelData other) {
if (other == null) return;
// 合并网格(避免名称冲突)
for (MeshData mesh : other.meshes) {
String originalName = mesh.name;
int counter = 1;
while (meshExists(mesh.name)) {
mesh.name = originalName + "_" + counter++;
}
this.meshes.add(mesh);
}
// 合并部件
for (PartData part : other.parts) {
String originalName = part.name;
int counter = 1;
while (partExists(part.name)) {
part.name = originalName + "_" + counter++;
}
this.parts.add(part);
}
// 合并参数
for (ParameterData param : other.parameters) {
this.parameters.add(param.copy());
}
// 合并纹理
for (TextureData texture : other.textures) {
String originalName = texture.name;
int counter = 1;
while (textureExists(texture.name)) {
texture.name = originalName + "_" + counter++;
}
this.textures.add(texture);
}
lastModifiedTime = System.currentTimeMillis();
}
private boolean partExists(String partName) {
return parts.stream().anyMatch(part -> part.name.equals(partName));
}
private boolean textureExists(String textureName) {
return textures.stream().anyMatch(texture -> texture.name.equals(textureName));
}
// ==================== 内部数据类 ====================
/**
* 部件数据
*/
public static class PartData implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public String parentName;
public Vector2f position;
public float rotation;
public Vector2f scale;
public boolean visible;
public float opacity;
public List<String> meshNames;
public Map<String, String> userData;
public PartData() {
this.position = new Vector2f();
this.rotation = 0.0f;
this.scale = new Vector2f(1.0f, 1.0f);
this.visible = true;
this.opacity = 1.0f;
this.meshNames = new ArrayList<>();
this.userData = new HashMap<>();
}
public PartData(ModelPart part) {
this();
this.name = part.getName();
this.position = part.getPosition();
this.rotation = part.getRotation();
this.scale = part.getScale();
this.visible = part.isVisible();
this.opacity = part.getOpacity();
// 收集网格名称
for (Mesh2D mesh : part.getMeshes()) {
this.meshNames.add(mesh.getName());
}
// 设置父级名称
if (part.getParent() != null) {
this.parentName = part.getParent().getName();
}
}
public ModelPart toModelPart(Map<String, Mesh2D> meshMap) {
ModelPart part = new ModelPart(name);
part.setPosition(position);
part.setRotation(rotation);
part.setScale(scale);
part.setVisible(visible);
part.setOpacity(opacity);
// 添加网格
for (String meshName : meshNames) {
Mesh2D mesh = meshMap.get(meshName);
if (mesh != null) {
part.addMesh(mesh);
}
}
return part;
}
public PartData copy() {
PartData copy = new PartData();
copy.name = this.name;
copy.parentName = this.parentName;
copy.position = new Vector2f(this.position);
copy.rotation = this.rotation;
copy.scale = new Vector2f(this.scale);
copy.visible = this.visible;
copy.opacity = this.opacity;
copy.meshNames = new ArrayList<>(this.meshNames);
copy.userData = new HashMap<>(this.userData);
return copy;
}
}
/**
* 网格数据
*/
public static class MeshData implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public float[] vertices;
public float[] uvs;
public int[] indices;
public String textureName;
public boolean visible;
public int drawMode;
public MeshData() {
this.visible = true;
this.drawMode = Mesh2D.TRIANGLES;
}
public MeshData(Mesh2D mesh) {
this();
this.name = mesh.getName();
this.vertices = mesh.getVertices();
this.uvs = mesh.getUVs();
this.indices = mesh.getIndices();
this.visible = mesh.isVisible();
this.drawMode = mesh.getDrawMode();
if (mesh.getTexture() != null) {
this.textureName = mesh.getTexture().getName();
}
}
public Mesh2D toMesh2D() {
Mesh2D mesh = new Mesh2D(name, vertices, uvs, indices);
mesh.setVisible(visible);
mesh.setDrawMode(drawMode);
return mesh;
}
public MeshData copy() {
MeshData copy = new MeshData();
copy.name = this.name;
copy.vertices = this.vertices != null ? this.vertices.clone() : null;
copy.uvs = this.uvs != null ? this.uvs.clone() : null;
copy.indices = this.indices != null ? this.indices.clone() : null;
copy.textureName = this.textureName;
copy.visible = this.visible;
copy.drawMode = this.drawMode;
return copy;
}
}
/**
* 纹理数据
*/
public static class TextureData implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public String filePath;
public byte[] imageData;
public int width;
public int height;
public Texture.TextureFormat format;
public TextureData() {}
public TextureData(Texture texture) {
this.name = texture.getName();
this.width = texture.getWidth();
this.height = texture.getHeight();
this.format = texture.getFormat();
}
public Texture toTexture() {
Texture texture = new Texture(name, width, height, format);
// 注意这里需要处理imageData的加载
// 实际项目中可能需要从文件路径加载图像数据
return texture;
}
public TextureData copy() {
TextureData copy = new TextureData();
copy.name = this.name;
copy.filePath = this.filePath;
copy.imageData = this.imageData != null ? this.imageData.clone() : null;
copy.width = this.width;
copy.height = this.height;
copy.format = this.format;
return copy;
}
}
/**
* 参数数据
*/
public static class ParameterData implements Serializable {
private static final long serialVersionUID = 1L;
public String id;
public float value;
public float defaultValue;
public float minValue;
public float maxValue;
public ParameterData() {}
public ParameterData(AnimationParameter param) {
this.id = param.getId();
this.value = param.getValue();
this.defaultValue = param.getDefaultValue();
this.minValue = param.getMinValue();
this.maxValue = param.getMaxValue();
}
public AnimationParameter toAnimationParameter() {
AnimationParameter param = new AnimationParameter(id, minValue, maxValue, defaultValue);
param.setValue(value); // 恢复保存时的值
return param;
}
public ParameterData copy() {
ParameterData copy = new ParameterData();
copy.id = this.id;
copy.value = this.value;
copy.defaultValue = this.defaultValue;
copy.minValue = this.minValue;
copy.maxValue = this.maxValue;
return copy;
}
}
/**
* 动画数据
*/
public static class AnimationData implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public float duration;
public boolean looping;
public Map<String, List<KeyframeData>> tracks;
public AnimationData() {
this.tracks = new HashMap<>();
}
public AnimationData copy() {
AnimationData copy = new AnimationData();
copy.name = this.name;
copy.duration = this.duration;
copy.looping = this.looping;
copy.tracks = new HashMap<>(this.tracks);
return copy;
}
}
/**
* 关键帧数据
*/
public static class KeyframeData implements Serializable {
private static final long serialVersionUID = 1L;
public float time;
public float value;
public String interpolation;
public KeyframeData copy() {
KeyframeData copy = new KeyframeData();
copy.time = this.time;
copy.value = this.value;
copy.interpolation = this.interpolation;
return copy;
}
}
// ==================== Getter/Setter ====================
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public UUID getUuid() { return uuid; }
public void setUuid(UUID uuid) { this.uuid = uuid; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public long getCreationTime() { return creationTime; }
public void setCreationTime(long creationTime) { this.creationTime = creationTime; }
public long getLastModifiedTime() { return lastModifiedTime; }
public void setLastModifiedTime(long lastModifiedTime) { this.lastModifiedTime = lastModifiedTime; }
public List<PartData> getParts() { return parts; }
public void setParts(List<PartData> parts) { this.parts = parts; }
public List<MeshData> getMeshes() { return meshes; }
public void setMeshes(List<MeshData> meshes) { this.meshes = meshes; }
public List<TextureData> getTextures() { return textures; }
public void setTextures(List<TextureData> textures) { this.textures = textures; }
public List<ParameterData> getParameters() { return parameters; }
public void setParameters(List<ParameterData> parameters) { this.parameters = parameters; }
public List<AnimationData> getAnimations() { return animations; }
public void setAnimations(List<AnimationData> animations) { this.animations = animations; }
public Vector2f getPivotPoint() { return pivotPoint; }
public void setPivotPoint(Vector2f pivotPoint) { this.pivotPoint = pivotPoint; }
public float getUnitsPerMeter() { return unitsPerMeter; }
public void setUnitsPerMeter(float unitsPerMeter) { this.unitsPerMeter = unitsPerMeter; }
public Map<String, String> getUserData() { return userData; }
public void setUserData(Map<String, String> userData) { this.userData = userData; }
// ==================== Object方法 ====================
@Override
public String toString() {
return "ModelData{" +
"name='" + name + '\'' +
", version='" + version + '\'' +
", parts=" + parts.size() +
", meshes=" + meshes.size() +
", parameters=" + parameters.size() +
", animations=" + animations.size() +
'}';
}
}

View File

@@ -0,0 +1,513 @@
package com.chuangzhou.vivid2D.render.model;
import com.chuangzhou.vivid2D.render.model.util.BoundingBox;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
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 java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 2D模型部件支持层级变换和变形器
* 使用 JOML 库进行数学计算
*
* @author tzdwindows 7
*/
public class ModelPart {
// ==================== 基础属性 ====================
private String name;
private ModelPart parent;
private final List<ModelPart> children;
private final List<Mesh2D> meshes;
// ==================== 变换属性 ====================
private final Vector2f position;
private float rotation;
private final Vector2f scale;
private final Matrix3f localTransform;
private final Matrix3f worldTransform;
// ==================== 渲染属性 ====================
private boolean visible;
private BlendMode blendMode;
private float opacity;
// ==================== 变形系统 ====================
private final List<Deformer> deformers;
// ==================== 状态标记 ====================
private boolean transformDirty;
private boolean boundsDirty;
// ==================== 构造器 ====================
public ModelPart() {
this("unnamed");
}
public ModelPart(String name) {
this.name = name;
this.children = new ArrayList<>();
this.meshes = new ArrayList<>();
this.deformers = new ArrayList<>();
// 初始化变换属性
this.position = new Vector2f();
this.rotation = 0.0f;
this.scale = new Vector2f(1.0f, 1.0f);
this.localTransform = new Matrix3f();
this.worldTransform = new Matrix3f();
// 初始化渲染属性
this.visible = true;
this.blendMode = BlendMode.NORMAL;
this.opacity = 1.0f;
// 标记需要更新
this.transformDirty = true;
this.boundsDirty = true;
updateLocalTransform();
}
// ==================== 层级管理 ====================
/**
* 添加子部件
*/
public void addChild(ModelPart child) {
if (child == this) {
throw new IllegalArgumentException("Cannot add self as child");
}
if (child.parent != null) {
child.parent.removeChild(child);
}
children.add(child);
child.parent = this;
markTransformDirty();
}
/**
* 移除子部件
*/
public boolean removeChild(ModelPart child) {
boolean removed = children.remove(child);
if (removed) {
child.parent = null;
markTransformDirty();
}
return removed;
}
/**
* 获取所有子部件
*/
public List<ModelPart> getChildren() {
return new ArrayList<>(children);
}
/**
* 根据名称查找子部件
*/
public ModelPart findChild(String name) {
for (ModelPart child : children) {
if (name.equals(child.getName())) {
return child;
}
}
return null;
}
/**
* 递归查找子部件
*/
public ModelPart findChildRecursive(String name) {
// 先检查直接子节点
ModelPart result = findChild(name);
if (result != null) {
return result;
}
// 递归检查子节点的子节点
for (ModelPart child : children) {
result = child.findChildRecursive(name);
if (result != null) {
return result;
}
}
return null;
}
// ==================== 变换系统 ====================
/**
* 更新世界变换
*/
public void updateWorldTransform(Matrix3f parentTransform, boolean recursive) {
// 如果需要更新局部变换
if (transformDirty) {
updateLocalTransform();
}
// 计算世界变换parent * local
parentTransform.mul(localTransform, worldTransform);
// 递归更新子部件
if (recursive) {
for (ModelPart child : children) {
child.updateWorldTransform(worldTransform, true);
}
}
// 标记边界需要更新
boundsDirty = true;
transformDirty = false;
}
/**
* 更新局部变换矩阵
*/
private void updateLocalTransform() {
float cos = (float) Math.cos(rotation);
float sin = (float) Math.sin(rotation);
// 正确的 R * S 组合(先 scale 再 rotate最终矩阵为 Translate * (Rotate * Scale)
float m00 = cos * scale.x; // = cos * sx
float m01 = -sin * scale.y; // = -sin * sy
float m02 = position.x; // tx
float m10 = sin * scale.x; // = sin * sx
float m11 = cos * scale.y; // = cos * sy
float m12 = position.y; // ty
localTransform.set(
m00, m01, m02,
m10, m11, m12,
0.0f, 0.0f, 1.0f
);
}
/**
* 标记变换需要更新
*/
public void markTransformDirty() {
this.transformDirty = true;
for (ModelPart child : children) {
child.markTransformDirty();
}
}
/**
* 设置位置
*/
public void setPosition(float x, float y) {
position.set(x, y);
markTransformDirty();
}
public void setPosition(Vector2f position) {
this.position.set(position);
markTransformDirty();
}
/**
* 移动部件
*/
public void translate(float dx, float dy) {
position.add(dx, dy);
markTransformDirty();
}
public void translate(Vector2f delta) {
position.add(delta);
markTransformDirty();
}
/**
* 设置旋转(弧度)
*/
public void setRotation(float radians) {
this.rotation = radians;
markTransformDirty();
}
/**
* 旋转部件
*/
public void rotate(float deltaRadians) {
this.rotation += deltaRadians;
markTransformDirty();
}
/**
* 设置缩放
*/
public void setScale(float sx, float sy) {
scale.set(sx, sy);
markTransformDirty();
}
public void setScale(float uniformScale) {
scale.set(uniformScale, uniformScale);
markTransformDirty();
}
public void setScale(Vector2f scale) {
this.scale.set(scale);
markTransformDirty();
}
/**
* 缩放部件
*/
public void scale(float sx, float sy) {
scale.mul(sx, sy);
markTransformDirty();
}
// ==================== 网格管理 ====================
/**
* 添加网格
*/
public void addMesh(Mesh2D mesh) {
meshes.add(mesh);
boundsDirty = true;
}
/**
* 移除网格
*/
public boolean removeMesh(Mesh2D mesh) {
boolean removed = meshes.remove(mesh);
if (removed) {
boundsDirty = true;
}
return removed;
}
/**
* 获取所有网格
*/
public List<Mesh2D> getMeshes() {
return new ArrayList<>(meshes);
}
// ==================== 变形器管理 ====================
/**
* 添加变形器
*/
public void addDeformer(Deformer deformer) {
deformers.add(deformer);
}
/**
* 移除变形器
*/
public boolean removeDeformer(Deformer deformer) {
return deformers.remove(deformer);
}
/**
* 应用参数到所有变形器
*/
public void applyParameter(AnimationParameter param) {
for (Deformer deformer : deformers) {
if (deformer.isDrivenBy(param.getId())) {
deformer.apply(param.getValue());
}
}
// 如果变形器改变了网格,需要更新边界
if (!deformers.isEmpty()) {
boundsDirty = true;
}
}
/**
* 应用所有变形器
*/
public void applyDeformers() {
for (Deformer deformer : deformers) {
for (Mesh2D mesh : meshes) {
deformer.applyToMesh(mesh);
}
}
boundsDirty = true;
}
// ==================== 工具方法 ====================
/**
* 变换点从局部空间到世界空间
*/
public Vector2f localToWorld(Vector2f localPoint) {
return Matrix3fUtils.transformPoint(worldTransform, localPoint);
}
/**
* 变换点从世界空间到局部空间
*/
public Vector2f worldToLocal(Vector2f worldPoint) {
return Matrix3fUtils.transformPointInverse(worldTransform, worldPoint);
}
/**
* 获取世界空间中的包围盒
*/
public BoundingBox getWorldBounds() {
if (boundsDirty) {
updateBounds();
}
BoundingBox worldBounds = new BoundingBox();
for (Mesh2D mesh : meshes) {
BoundingBox meshBounds = mesh.getBounds();
if (meshBounds != null) {
// 变换到世界空间
Vector2f min = localToWorld(new Vector2f(meshBounds.getMinX(), meshBounds.getMinY()));
Vector2f max = localToWorld(new Vector2f(meshBounds.getMaxX(), meshBounds.getMaxY()));
worldBounds.expand(min.x, min.y);
worldBounds.expand(max.x, max.y);
}
}
return worldBounds;
}
/**
* 更新边界
*/
private void updateBounds() {
for (Mesh2D mesh : meshes) {
mesh.updateBounds();
}
boundsDirty = false;
}
/**
* 检查是否可见(考虑父级可见性)
*/
public boolean isEffectivelyVisible() {
if (!visible) {
return false;
}
if (parent != null) {
return parent.isEffectivelyVisible();
}
return true;
}
// ==================== Getter/Setter ====================
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ModelPart getParent() {
return parent;
}
public Vector2f getPosition() {
return new Vector2f(position);
}
public float getRotation() {
return rotation;
}
public Vector2f getScale() {
return new Vector2f(scale);
}
public Matrix3f getLocalTransform() {
return new Matrix3f(localTransform);
}
public Matrix3f getWorldTransform() {
return new Matrix3f(worldTransform);
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public BlendMode getBlendMode() {
return blendMode;
}
public void setBlendMode(BlendMode blendMode) {
this.blendMode = blendMode;
}
public float getOpacity() {
return opacity;
}
public void setOpacity(float opacity) {
this.opacity = Math.max(0.0f, Math.min(1.0f, opacity));
}
public List<Deformer> getDeformers() {
return new ArrayList<>(deformers);
}
// ==================== 枚举和内部类 ====================
/**
* 混合模式枚举
*/
public enum BlendMode {
NORMAL,
ADDITIVE,
MULTIPLY,
SCREEN
}
// ==================== Object 方法 ====================
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModelPart modelPart = (ModelPart) o;
return Float.compare(rotation, modelPart.rotation) == 0 &&
visible == modelPart.visible &&
Float.compare(opacity, modelPart.opacity) == 0 &&
Objects.equals(name, modelPart.name) &&
Objects.equals(position, modelPart.position) &&
Objects.equals(scale, modelPart.scale);
}
@Override
public int hashCode() {
return Objects.hash(name, position, rotation, scale, visible, opacity);
}
@Override
public String toString() {
return "ModelPart{" +
"name='" + name + '\'' +
", position=" + position +
", rotation=" + rotation +
", scale=" + scale +
", visible=" + visible +
", children=" + children.size() +
", meshes=" + meshes.size() +
'}';
}
}

View File

@@ -0,0 +1,90 @@
package com.chuangzhou.vivid2D.render.model.transform;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Vector2f;
import java.util.List;
/**
* 旋转变形器 - 围绕中心点旋转顶点
*/
public class RotationDeformer extends Deformer {
private float baseAngle = 0.0f;
private float angleRange = (float) Math.PI; // ±90度范围
private float currentAngle = 0.0f;
public RotationDeformer(String name) {
super(name);
}
public RotationDeformer(String name, Vector2f center, float radius) {
super(name);
this.getRange().setCenter(center);
this.getRange().setRadius(radius);
}
@Override
public void applyToMesh(Mesh2D mesh) {
if (!enabled || weight <= 0.0f || currentAngle == 0.0f) {
return;
}
float[] vertices = mesh.getVertices(); // 获取顶点数组副本
Vector2f center = getRange().getCenter();
float cos = (float) Math.cos(currentAngle);
float sin = (float) Math.sin(currentAngle);
boolean modified = false;
for (int i = 0; i < mesh.getVertexCount(); i++) {
int baseIndex = i * 2;
float originalX = vertices[baseIndex];
float originalY = vertices[baseIndex + 1];
// 计算相对于中心的坐标
float dx = originalX - center.x;
float dy = originalY - center.y;
// 应用旋转
float rotatedX = dx * cos - dy * sin;
float rotatedY = dx * sin + dy * cos;
float deformedX = center.x + rotatedX;
float deformedY = center.y + rotatedY;
// 应用变形权重
float deformationWeight = computeDeformationWeight(originalX, originalY);
blendVertexPosition(vertices, i, originalX, originalY, deformedX, deformedY, deformationWeight);
modified = true;
}
if (modified) {
// 更新网格顶点数据
for (int i = 0; i < mesh.getVertexCount(); i++) {
int baseIndex = i * 2;
mesh.setVertex(i, vertices[baseIndex], vertices[baseIndex + 1]);
}
}
}
@Override
public void apply(float value) {
// value 范围 [0, 1] 映射到 [baseAngle - angleRange/2, baseAngle + angleRange/2]
this.currentAngle = baseAngle + (value - 0.5f) * angleRange;
}
@Override
public void reset() {
this.currentAngle = baseAngle;
}
// Getter/Setter
public float getBaseAngle() { return baseAngle; }
public void setBaseAngle(float baseAngle) { this.baseAngle = baseAngle; }
public float getAngleRange() { return angleRange; }
public void setAngleRange(float angleRange) { this.angleRange = angleRange; }
public float getCurrentAngle() { return currentAngle; }
}

View File

@@ -0,0 +1,83 @@
package com.chuangzhou.vivid2D.render.model.transform;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Vector2f;
import java.util.List;
/**
* 缩放变形器 - 围绕中心点缩放顶点
*/
public class ScaleDeformer extends Deformer {
private Vector2f baseScale = new Vector2f(1.0f, 1.0f);
private Vector2f scaleRange = new Vector2f(0.5f, 0.5f); // 缩放范围
private Vector2f currentScale = new Vector2f(1.0f, 1.0f);
public ScaleDeformer(String name) {
super(name);
}
@Override
public void applyToMesh(Mesh2D mesh) {
if (!enabled || weight <= 0.0f ||
(currentScale.x == 1.0f && currentScale.y == 1.0f)) {
return;
}
float[] vertices = mesh.getVertices(); // 获取顶点数组副本
Vector2f center = getRange().getCenter();
boolean modified = false;
for (int i = 0; i < mesh.getVertexCount(); i++) {
int baseIndex = i * 2;
float originalX = vertices[baseIndex];
float originalY = vertices[baseIndex + 1];
// 计算相对于中心的坐标
float dx = originalX - center.x;
float dy = originalY - center.y;
// 应用缩放
float deformedX = center.x + dx * currentScale.x;
float deformedY = center.y + dy * currentScale.y;
// 应用变形权重
float deformationWeight = computeDeformationWeight(originalX, originalY);
blendVertexPosition(vertices, i, originalX, originalY, deformedX, deformedY, deformationWeight);
modified = true;
}
if (modified) {
// 更新网格顶点数据
for (int i = 0; i < mesh.getVertexCount(); i++) {
int baseIndex = i * 2;
mesh.setVertex(i, vertices[baseIndex], vertices[baseIndex + 1]);
}
}
}
@Override
public void apply(float value) {
// value 范围 [0, 1] 映射到缩放范围
float scaleX = baseScale.x + (value - 0.5f) * scaleRange.x;
float scaleY = baseScale.y + (value - 0.5f) * scaleRange.y;
this.currentScale.set(scaleX, scaleY);
}
@Override
public void reset() {
this.currentScale.set(baseScale);
}
// Getter/Setter
public Vector2f getBaseScale() { return new Vector2f(baseScale); }
public void setBaseScale(Vector2f baseScale) { this.baseScale.set(baseScale); }
public Vector2f getScaleRange() { return new Vector2f(scaleRange); }
public void setScaleRange(Vector2f scaleRange) { this.scaleRange.set(scaleRange); }
public Vector2f getCurrentScale() { return new Vector2f(currentScale); }
}

View File

@@ -0,0 +1,277 @@
package com.chuangzhou.vivid2D.render.model.transform;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Vector2f;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 顶点位置变形器 - 直接修改顶点位置
* 使用高效的数据结构存储变形数据
*
* @author tzdwindows 7
*/
public class VertexDeformer extends Deformer {
// 使用更高效的数据结构
private final Map<Integer, VertexDeformation> vertexDeformations;
private final List<Integer> vertexIndexList; // 用于快速迭代
private float currentValue = 0.0f;
public VertexDeformer(String name) {
super(name);
this.vertexDeformations = new HashMap<>();
this.vertexIndexList = new ArrayList<>();
}
/**
* 顶点变形数据内部类
*/
private static class VertexDeformation {
final float originalX;
final float originalY;
final float targetX;
final float targetY;
VertexDeformation(float originalX, float originalY, float targetX, float targetY) {
this.originalX = originalX;
this.originalY = originalY;
this.targetX = targetX;
this.targetY = targetY;
}
}
/**
* 添加顶点变形目标
*/
public void addVertexDeformation(int vertexIndex, float originalX, float originalY, float targetX, float targetY) {
VertexDeformation deformation = new VertexDeformation(originalX, originalY, targetX, targetY);
// 使用HashMap避免重复顶点索引
if (!vertexDeformations.containsKey(vertexIndex)) {
vertexIndexList.add(vertexIndex);
}
vertexDeformations.put(vertexIndex, deformation);
}
/**
* 添加顶点变形目标使用Vector2f
*/
public void addVertexDeformation(int vertexIndex, Vector2f originalPos, Vector2f targetPos) {
addVertexDeformation(vertexIndex, originalPos.x, originalPos.y, targetPos.x, targetPos.y);
}
/**
* 批量添加顶点变形
*/
public void addVertexDeformations(Map<Integer, VertexDeformation> deformations) {
for (Map.Entry<Integer, VertexDeformation> entry : deformations.entrySet()) {
addVertexDeformation(entry.getKey(),
entry.getValue().originalX, entry.getValue().originalY,
entry.getValue().targetX, entry.getValue().targetY);
}
}
/**
* 移除顶点变形
*/
public boolean removeVertexDeformation(int vertexIndex) {
VertexDeformation removed = vertexDeformations.remove(vertexIndex);
if (removed != null) {
vertexIndexList.remove((Integer) vertexIndex); // 注意要移除对象而不是索引
return true;
}
return false;
}
/**
* 清空所有顶点变形
*/
public void clearVertexDeformations() {
vertexDeformations.clear();
vertexIndexList.clear();
}
/**
* 检查是否包含指定顶点的变形
*/
public boolean containsVertexDeformation(int vertexIndex) {
return vertexDeformations.containsKey(vertexIndex);
}
/**
* 获取顶点变形数量
*/
public int getVertexDeformationCount() {
return vertexDeformations.size();
}
/**
* 获取指定顶点的变形数据
*/
public VertexDeformation getVertexDeformation(int vertexIndex) {
return vertexDeformations.get(vertexIndex);
}
/**
* 获取所有受影响的顶点索引
*/
public List<Integer> getAffectedVertexIndices() {
return new ArrayList<>(vertexIndexList);
}
@Override
public void applyToMesh(Mesh2D mesh) {
if (!enabled || weight <= 0.0f || vertexDeformations.isEmpty()) {
return;
}
float[] vertices = mesh.getVertices(); // 获取顶点数组副本
boolean modified = false;
// 使用预存的索引列表进行快速迭代
for (int vertexIndex : vertexIndexList) {
if (vertexIndex < 0 || vertexIndex >= mesh.getVertexCount()) {
continue;
}
VertexDeformation deformation = vertexDeformations.get(vertexIndex);
if (deformation == null) {
continue;
}
// 获取当前顶点位置
int vertexBaseIndex = vertexIndex * 2;
float currentX = vertices[vertexBaseIndex];
float currentY = vertices[vertexBaseIndex + 1];
// 计算变形位置
float deformedX = deformation.originalX +
(deformation.targetX - deformation.originalX) * currentValue;
float deformedY = deformation.originalY +
(deformation.targetY - deformation.originalY) * currentValue;
// 应用变形权重
float deformationWeight = computeDeformationWeight(currentX, currentY);
blendVertexPosition(vertices, vertexIndex, currentX, currentY,
deformedX, deformedY, deformationWeight);
modified = true;
}
if (modified) {
// 批量更新网格顶点数据
updateMeshVertices(mesh, vertices);
}
}
/**
* 批量更新网格顶点(优化性能)
*/
private void updateMeshVertices(Mesh2D mesh, float[] vertices) {
// 只更新受影响的顶点,而不是全部顶点
for (int vertexIndex : vertexIndexList) {
if (vertexIndex < 0 || vertexIndex >= mesh.getVertexCount()) {
continue;
}
int vertexBaseIndex = vertexIndex * 2;
mesh.setVertex(vertexIndex, vertices[vertexBaseIndex], vertices[vertexBaseIndex + 1]);
}
}
@Override
public void apply(float value) {
this.currentValue = Math.max(0.0f, Math.min(1.0f, value));
}
@Override
public void reset() {
this.currentValue = 0.0f;
}
/**
* 设置当前值并立即应用到指定网格
*/
public void applyToMesh(float value, Mesh2D mesh) {
apply(value);
applyToMesh(mesh);
}
/**
* 插值动画到目标值
*/
public void animateTo(float targetValue, float duration) {
// 这里可以实现动画插值逻辑
// 实际项目中可以使用动画系统
this.currentValue = targetValue;
}
public float getCurrentValue() {
return currentValue;
}
/**
* 创建顶点变形器的深拷贝
*/
public VertexDeformer copy() {
VertexDeformer copy = new VertexDeformer(this.name + "_copy");
copy.enabled = this.enabled;
copy.weight = this.weight;
copy.currentValue = this.currentValue;
// 深拷贝变形数据
for (Map.Entry<Integer, VertexDeformation> entry : this.vertexDeformations.entrySet()) {
VertexDeformation deformation = entry.getValue();
copy.addVertexDeformation(entry.getKey(),
deformation.originalX, deformation.originalY,
deformation.targetX, deformation.targetY);
}
return copy;
}
/**
* 从原始网格自动提取原始位置
*/
public void extractOriginalPositionsFromMesh(Mesh2D mesh) {
for (int vertexIndex : vertexIndexList) {
if (vertexIndex < 0 || vertexIndex >= mesh.getVertexCount()) {
continue;
}
Vector2f currentPos = mesh.getVertex(vertexIndex);
VertexDeformation deformation = vertexDeformations.get(vertexIndex);
if (deformation != null) {
// 更新原始位置为当前网格位置
addVertexDeformation(vertexIndex,
currentPos.x, currentPos.y,
deformation.targetX, deformation.targetY);
}
}
}
/**
* 反转变形方向(交换原始位置和目标位置)
*/
public void reverseDeformation() {
Map<Integer, VertexDeformation> reversed = new HashMap<>();
for (Map.Entry<Integer, VertexDeformation> entry : vertexDeformations.entrySet()) {
VertexDeformation original = entry.getValue();
VertexDeformation reversedDeformation = new VertexDeformation(
original.targetX, original.targetY,
original.originalX, original.originalY
);
reversed.put(entry.getKey(), reversedDeformation);
}
this.vertexDeformations.clear();
this.vertexDeformations.putAll(reversed);
this.currentValue = 1.0f - this.currentValue; // 反转当前值
}
}

View File

@@ -0,0 +1,288 @@
package com.chuangzhou.vivid2D.render.model.transform;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Vector2f;
import java.util.List;
/**
* 波浪变形器 - 创建波浪效果的顶点变形
*/
public class WaveDeformer extends Deformer {
private float amplitude = 10.0f; // 波幅
private float frequency = 0.1f; // 频率
private float phase = 0.0f; // 相位
private float waveAngle = 0.0f; // 波传播方向角度
private float currentTime = 0.0f;
public WaveDeformer(String name) {
super(name);
}
@Override
public void applyToMesh(Mesh2D mesh) {
if (!enabled || weight <= 0.0f || amplitude == 0.0f) {
return;
}
float[] vertices = mesh.getVertices(); // 获取顶点数组副本
Vector2f center = getRange().getCenter();
float cosDir = (float) Math.cos(waveAngle);
float sinDir = (float) Math.sin(waveAngle);
boolean modified = false;
for (int i = 0; i < mesh.getVertexCount(); i++) {
int baseIndex = i * 2;
float originalX = vertices[baseIndex];
float originalY = vertices[baseIndex + 1];
// 计算在波传播方向上的投影距离
float projDistance = (originalX - center.x) * cosDir +
(originalY - center.y) * sinDir;
// 计算波浪偏移
float waveOffset = amplitude *
(float) Math.sin(frequency * projDistance + phase + currentTime);
// 垂直于波传播方向的偏移
float deformedX = originalX - sinDir * waveOffset;
float deformedY = originalY + cosDir * waveOffset;
// 应用变形权重
float deformationWeight = computeDeformationWeight(originalX, originalY);
blendVertexPosition(vertices, i, originalX, originalY, deformedX, deformedY, deformationWeight);
modified = true;
}
if (modified) {
// 更新网格顶点数据
for (int i = 0; i < mesh.getVertexCount(); i++) {
int baseIndex = i * 2;
mesh.setVertex(i, vertices[baseIndex], vertices[baseIndex + 1]);
}
}
}
@Override
public void apply(float value) {
// 根据配置的驱动参数类型决定如何应用value
// 这里假设通过参数名称或配置来决定控制哪个波浪参数
// 方案1: 根据当前激活的驱动参数类型来应用
if (!parameterValues.isEmpty()) {
// 如果有多个参数,可以按优先级或特定逻辑处理
// 这里简单取第一个参数的值
String firstParam = drivenParameters.iterator().next();
applyByParameterName(firstParam, value);
} else {
// 默认行为:控制波幅
applyAmplitude(value);
}
}
/**
* 根据参数名称应用不同的波浪参数控制
*/
private void applyByParameterName(String paramName, float value) {
paramName = paramName.toLowerCase();
if (paramName.contains("amplitude") || paramName.contains("amp")) {
applyAmplitude(value);
} else if (paramName.contains("frequency") || paramName.contains("freq")) {
applyFrequency(value);
} else if (paramName.contains("phase") || paramName.contains("offset")) {
applyPhase(value);
} else if (paramName.contains("angle") || paramName.contains("direction")) {
applyWaveAngle(value);
} else if (paramName.contains("time") || paramName.contains("speed")) {
applyTimeSpeed(value);
} else if (paramName.contains("weight") || paramName.contains("intensity")) {
applyWeight(value);
} else {
// 默认控制波幅
applyAmplitude(value);
}
}
/**
* 控制波幅 - value [0,1] 映射到 [0, maxAmplitude]
*/
private void applyAmplitude(float value) {
float maxAmplitude = 50.0f; // 最大波幅
this.amplitude = value * maxAmplitude;
}
/**
* 控制频率 - value [0,1] 映射到 [minFrequency, maxFrequency]
*/
private void applyFrequency(float value) {
float minFrequency = 0.01f; // 最小频率
float maxFrequency = 0.5f; // 最大频率
this.frequency = minFrequency + value * (maxFrequency - minFrequency);
}
/**
* 控制相位 - value [0,1] 映射到 [0, 2π]
*/
private void applyPhase(float value) {
this.phase = value * (float) (2.0f * Math.PI);
}
/**
* 控制波传播方向 - value [0,1] 映射到 [0, 2π]
*/
private void applyWaveAngle(float value) {
this.waveAngle = value * (float) (2.0f * Math.PI);
}
/**
* 控制时间速度 - value [0,1] 映射到时间乘数 [0.1, 5.0]
*/
private void applyTimeSpeed(float value) {
// 这个需要在外部update方法中使用timeMultiplier
// 这里先存储在update中使用
this.timeMultiplier = 0.1f + value * 4.9f;
}
/**
* 控制变形器权重 - value [0,1] 直接设置权重
*/
private void applyWeight(float value) {
setWeight(value);
}
// 添加时间乘数字段
private float timeMultiplier = 1.0f;
/**
* 更新波浪动画(使用时间乘数)
*/
public void update(float deltaTime) {
this.currentTime += deltaTime * timeMultiplier;
}
// 添加参数配置方法,允许外部指定控制模式
public enum ControlMode {
AMPLITUDE, // 控制波幅
FREQUENCY, // 控制频率
PHASE, // 控制相位
WAVE_ANGLE, // 控制波方向
TIME_SPEED, // 控制动画速度
WEIGHT // 控制变形器权重
}
private ControlMode controlMode = ControlMode.AMPLITUDE;
/**
* 设置控制模式
*/
public void setControlMode(ControlMode mode) {
this.controlMode = mode;
}
/**
* 根据设置的控制模式应用参数
*/
public void applyWithMode(float value, ControlMode mode) {
switch (mode) {
case AMPLITUDE:
applyAmplitude(value);
break;
case FREQUENCY:
applyFrequency(value);
break;
case PHASE:
applyPhase(value);
break;
case WAVE_ANGLE:
applyWaveAngle(value);
break;
case TIME_SPEED:
applyTimeSpeed(value);
break;
case WEIGHT:
applyWeight(value);
break;
default:
applyAmplitude(value);
}
}
/**
* 批量应用多个参数
*/
public void applyParameters(float amplitudeValue, float frequencyValue, float phaseValue,
float angleValue, float speedValue, float weightValue) {
applyAmplitude(amplitudeValue);
applyFrequency(frequencyValue);
applyPhase(phaseValue);
applyWaveAngle(angleValue);
applyTimeSpeed(speedValue);
applyWeight(weightValue);
}
/**
* 使用配置对象应用参数
*/
public void applyFromConfig(WaveConfig config) {
this.amplitude = config.amplitude;
this.frequency = config.frequency;
this.phase = config.phase;
this.waveAngle = config.waveAngle;
this.timeMultiplier = config.timeMultiplier;
setWeight(config.weight);
}
/**
* 波浪配置类
*/
public static class WaveConfig {
public float amplitude = 10.0f;
public float frequency = 0.1f;
public float phase = 0.0f;
public float waveAngle = 0.0f;
public float timeMultiplier = 1.0f;
public float weight = 1.0f;
public WaveConfig() {}
public WaveConfig(float amplitude, float frequency, float phase,
float waveAngle, float timeMultiplier, float weight) {
this.amplitude = amplitude;
this.frequency = frequency;
this.phase = phase;
this.waveAngle = waveAngle;
this.timeMultiplier = timeMultiplier;
this.weight = weight;
}
}
@Override
public void reset() {
this.currentTime = 0.0f;
this.amplitude = 10.0f;
}
public float getTimeMultiplier() { return timeMultiplier; }
public void setTimeMultiplier(float timeMultiplier) { this.timeMultiplier = timeMultiplier; }
public ControlMode getControlMode() { return controlMode; }
// Getter/Setter
public float getAmplitude() { return amplitude; }
public void setAmplitude(float amplitude) { this.amplitude = amplitude; }
public float getFrequency() { return frequency; }
public void setFrequency(float frequency) { this.frequency = frequency; }
public float getPhase() { return phase; }
public void setPhase(float phase) { this.phase = phase; }
public float getWaveAngle() { return waveAngle; }
public void setWaveAngle(float waveAngle) { this.waveAngle = waveAngle; }
public float getCurrentTime() { return currentTime; }
public void setCurrentTime(float currentTime) { this.currentTime = currentTime; }
}

View File

@@ -0,0 +1,717 @@
package com.chuangzhou.vivid2D.render.model.util;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 动画剪辑类用于管理2D模型的完整动画序列
* 支持关键帧动画、曲线编辑、事件标记和动画混合
*
* @author tzdwindows 7
*/
public class AnimationClip {
// ==================== 剪辑属性 ====================
private final String name;
private final UUID uuid;
private float duration;
private float framesPerSecond;
private boolean looping;
// ==================== 动画数据 ====================
private final Map<String, AnimationCurve> curves;
private final List<AnimationEventMarker> eventMarkers;
private final Map<String, Float> defaultValues;
// ==================== 元数据 ====================
private String author;
private String description;
private long creationTime;
private long lastModifiedTime;
private Map<String, String> userData;
// ==================== 构造器 ====================
public AnimationClip(String name) {
this(name, 1.0f, 60.0f);
}
public AnimationClip(String name, float duration, float fps) {
this.name = name;
this.uuid = UUID.randomUUID();
this.duration = Math.max(0.0f, duration);
this.framesPerSecond = Math.max(1.0f, fps);
this.looping = true;
this.curves = new ConcurrentHashMap<>();
this.eventMarkers = new ArrayList<>();
this.defaultValues = new ConcurrentHashMap<>();
this.author = "Unknown";
this.description = "";
this.creationTime = System.currentTimeMillis();
this.lastModifiedTime = creationTime;
this.userData = new ConcurrentHashMap<>();
}
// ==================== 曲线管理 ====================
/**
* 添加动画曲线
*/
public AnimationCurve addCurve(String parameterId) {
return addCurve(parameterId, 0.0f);
}
public AnimationCurve addCurve(String parameterId, float defaultValue) {
AnimationCurve curve = new AnimationCurve(parameterId, defaultValue);
curves.put(parameterId, curve);
defaultValues.put(parameterId, defaultValue);
markModified();
return curve;
}
/**
* 获取动画曲线
*/
public AnimationCurve getCurve(String parameterId) {
return curves.get(parameterId);
}
/**
* 移除动画曲线
*/
public boolean removeCurve(String parameterId) {
boolean removed = curves.remove(parameterId) != null;
defaultValues.remove(parameterId);
if (removed) markModified();
return removed;
}
/**
* 检查是否存在曲线
*/
public boolean hasCurve(String parameterId) {
return curves.containsKey(parameterId);
}
/**
* 获取所有曲线参数ID
*/
public Set<String> getCurveParameterIds() {
return Collections.unmodifiableSet(curves.keySet());
}
// ==================== 关键帧管理 ====================
/**
* 添加关键帧
*/
public Keyframe addKeyframe(String parameterId, float time, float value) {
return addKeyframe(parameterId, time, value, InterpolationType.LINEAR);
}
public Keyframe addKeyframe(String parameterId, float time, float value,
InterpolationType interpolation) {
AnimationCurve curve = getOrCreateCurve(parameterId);
Keyframe keyframe = curve.addKeyframe(time, value, interpolation);
updateDurationIfNeeded(time);
markModified();
return keyframe;
}
/**
* 移除关键帧
*/
public boolean removeKeyframe(String parameterId, float time) {
AnimationCurve curve = curves.get(parameterId);
if (curve != null) {
boolean removed = curve.removeKeyframe(time);
if (removed) markModified();
return removed;
}
return false;
}
/**
* 获取关键帧
*/
public Keyframe getKeyframe(String parameterId, float time) {
AnimationCurve curve = curves.get(parameterId);
return curve != null ? curve.getKeyframe(time) : null;
}
/**
* 获取参数在指定时间的所有关键帧
*/
public List<Keyframe> getKeyframes(String parameterId) {
AnimationCurve curve = curves.get(parameterId);
return curve != null ? curve.getKeyframes() : Collections.emptyList();
}
// ==================== 采样系统 ====================
/**
* 采样动画在指定时间的参数值
*/
public Map<String, Float> sample(float time) {
Map<String, Float> result = new HashMap<>();
for (Map.Entry<String, AnimationCurve> entry : curves.entrySet()) {
String paramId = entry.getKey();
AnimationCurve curve = entry.getValue();
float value = curve.sample(time);
result.put(paramId, value);
}
return result;
}
/**
* 采样单个参数在指定时间的值
*/
public float sampleParameter(String parameterId, float time) {
AnimationCurve curve = curves.get(parameterId);
return curve != null ? curve.sample(time) : defaultValues.getOrDefault(parameterId, 0.0f);
}
/**
* 采样动画在指定时间的参数值(应用循环)
*/
public Map<String, Float> sampleLooped(float time) {
float effectiveTime = time;
if (looping && duration > 0) {
effectiveTime = time % duration;
} else {
effectiveTime = Math.min(time, duration);
}
return sample(effectiveTime);
}
// ==================== 事件标记管理 ====================
/**
* 添加事件标记
*/
public AnimationEventMarker addEventMarker(String name, float time) {
return addEventMarker(name, time, null);
}
public AnimationEventMarker addEventMarker(String name, float time, Runnable action) {
AnimationEventMarker marker = new AnimationEventMarker(name, time, action);
// 按时间排序插入
int index = 0;
while (index < eventMarkers.size() && eventMarkers.get(index).getTime() < time) {
index++;
}
eventMarkers.add(index, marker);
updateDurationIfNeeded(time);
markModified();
return marker;
}
/**
* 移除事件标记
*/
public boolean removeEventMarker(String name) {
return eventMarkers.removeIf(marker -> marker.getName().equals(name));
}
/**
* 获取指定时间范围内的事件标记
*/
public List<AnimationEventMarker> getEventMarkersInRange(float startTime, float endTime) {
List<AnimationEventMarker> result = new ArrayList<>();
for (AnimationEventMarker marker : eventMarkers) {
if (marker.getTime() >= startTime && marker.getTime() <= endTime) {
result.add(marker);
}
}
return result;
}
/**
* 触发指定时间的事件标记
*/
public void triggerEventMarkers(float time, float tolerance) {
for (AnimationEventMarker marker : eventMarkers) {
if (Math.abs(marker.getTime() - time) <= tolerance && !marker.isTriggered()) {
marker.trigger();
}
}
}
/**
* 重置所有事件标记状态
*/
public void resetEventMarkers() {
for (AnimationEventMarker marker : eventMarkers) {
marker.reset();
}
}
// ==================== 工具方法 ====================
/**
* 获取或创建曲线
*/
private AnimationCurve getOrCreateCurve(String parameterId) {
return curves.computeIfAbsent(parameterId, k -> {
float defaultValue = defaultValues.getOrDefault(parameterId, 0.0f);
return new AnimationCurve(parameterId, defaultValue);
});
}
/**
* 更新动画时长(如果需要)
*/
private void updateDurationIfNeeded(float time) {
if (time > duration) {
duration = time;
markModified();
}
}
/**
* 标记为已修改
*/
private void markModified() {
lastModifiedTime = System.currentTimeMillis();
}
/**
* 计算帧数
*/
public int getFrameCount() {
return (int) Math.ceil(duration * framesPerSecond);
}
/**
* 时间转换为帧索引
*/
public int timeToFrame(float time) {
return (int) (time * framesPerSecond);
}
/**
* 帧索引转换为时间
*/
public float frameToTime(int frame) {
return frame / framesPerSecond;
}
/**
* 检查时间是否在动画范围内
*/
public boolean isTimeInRange(float time) {
return time >= 0 && time <= duration;
}
/**
* 获取动画的边界值(最小/最大值)
*/
public Map<String, float[]> getValueBounds() {
Map<String, float[]> bounds = new HashMap<>();
for (Map.Entry<String, AnimationCurve> entry : curves.entrySet()) {
String paramId = entry.getKey();
AnimationCurve curve = entry.getValue();
float[] minMax = curve.getValueRange();
bounds.put(paramId, minMax);
}
return bounds;
}
/**
* 创建动画剪辑的深拷贝
*/
public AnimationClip copy() {
AnimationClip copy = new AnimationClip(name + "_copy", duration, framesPerSecond);
copy.looping = this.looping;
copy.author = this.author;
copy.description = this.description;
// 深拷贝曲线
for (Map.Entry<String, AnimationCurve> entry : this.curves.entrySet()) {
copy.curves.put(entry.getKey(), entry.getValue().copy());
}
// 深拷贝默认值
copy.defaultValues.putAll(this.defaultValues);
// 深拷贝事件标记
for (AnimationEventMarker marker : this.eventMarkers) {
copy.eventMarkers.add(marker.copy());
}
// 深拷贝用户数据
copy.userData.putAll(this.userData);
return copy;
}
/**
* 合并另一个动画剪辑
*/
public void merge(AnimationClip other) {
if (other == null) return;
// 合并曲线
for (Map.Entry<String, AnimationCurve> entry : other.curves.entrySet()) {
String paramId = entry.getKey();
AnimationCurve otherCurve = entry.getValue();
if (this.curves.containsKey(paramId)) {
// 合并到现有曲线
AnimationCurve thisCurve = this.curves.get(paramId);
for (Keyframe keyframe : otherCurve.getKeyframes()) {
thisCurve.addKeyframe(keyframe.getTime(), keyframe.getValue(),
keyframe.getInterpolation());
}
} else {
// 添加新曲线
this.curves.put(paramId, otherCurve.copy());
}
}
// 合并事件标记
for (AnimationEventMarker marker : other.eventMarkers) {
this.addEventMarker(marker.getName() + "_merged", marker.getTime(),
marker.getAction());
}
// 更新时长
this.duration = Math.max(this.duration, other.duration);
markModified();
}
// ==================== 内部类 ====================
/**
* 动画曲线类
*/
public static class AnimationCurve {
private final String parameterId;
private final List<Keyframe> keyframes;
private final float defaultValue;
public AnimationCurve(String parameterId, float defaultValue) {
this.parameterId = parameterId;
this.keyframes = new ArrayList<>();
this.defaultValue = defaultValue;
}
/**
* 添加关键帧
*/
public Keyframe addKeyframe(float time, float value) {
return addKeyframe(time, value, InterpolationType.LINEAR);
}
public Keyframe addKeyframe(float time, float value, InterpolationType interpolation) {
Keyframe keyframe = new Keyframe(time, value, interpolation);
// 移除相同时间的关键帧(如果有)
removeKeyframe(time);
// 按时间排序插入
int index = 0;
while (index < keyframes.size() && keyframes.get(index).getTime() < time) {
index++;
}
keyframes.add(index, keyframe);
return keyframe;
}
/**
* 移除关键帧
*/
public boolean removeKeyframe(float time) {
return keyframes.removeIf(kf -> Math.abs(kf.getTime() - time) < 0.0001f);
}
/**
* 获取关键帧
*/
public Keyframe getKeyframe(float time) {
for (Keyframe kf : keyframes) {
if (Math.abs(kf.getTime() - time) < 0.0001f) {
return kf;
}
}
return null;
}
/**
* 采样曲线值
*/
public float sample(float time) {
if (keyframes.isEmpty()) {
return defaultValue;
}
// 在第一个关键帧之前
if (time <= keyframes.get(0).getTime()) {
return keyframes.get(0).getValue();
}
// 在最后一个关键帧之后
if (time >= keyframes.get(keyframes.size() - 1).getTime()) {
return keyframes.get(keyframes.size() - 1).getValue();
}
// 找到包围时间的关键帧
for (int i = 0; i < keyframes.size() - 1; i++) {
Keyframe kf1 = keyframes.get(i);
Keyframe kf2 = keyframes.get(i + 1);
if (time >= kf1.getTime() && time <= kf2.getTime()) {
return interpolate(kf1, kf2, time);
}
}
return defaultValue;
}
/**
* 插值计算
*/
private float interpolate(Keyframe kf1, Keyframe kf2, float time) {
float t = (time - kf1.getTime()) / (kf2.getTime() - kf1.getTime());
switch (kf1.getInterpolation()) {
case LINEAR:
return lerp(kf1.getValue(), kf2.getValue(), t);
case STEP:
return kf1.getValue();
case SMOOTH:
return smoothLerp(kf1.getValue(), kf2.getValue(), t);
case EASE_IN:
return easeInLerp(kf1.getValue(), kf2.getValue(), t);
case EASE_OUT:
return easeOutLerp(kf1.getValue(), kf2.getValue(), t);
case EASE_IN_OUT:
return easeInOutLerp(kf1.getValue(), kf2.getValue(), t);
default:
return kf1.getValue();
}
}
private float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
private float smoothLerp(float a, float b, float t) {
float t2 = t * t;
float t3 = t2 * t;
return a * (2 * t3 - 3 * t2 + 1) + b * (-2 * t3 + 3 * t2);
}
private float easeInLerp(float a, float b, float t) {
return a + (b - a) * (t * t);
}
private float easeOutLerp(float a, float b, float t) {
return a + (b - a) * (1 - (1 - t) * (1 - t));
}
private float easeInOutLerp(float a, float b, float t) {
return a + (b - a) * ((t < 0.5f) ? 2 * t * t : 1 - (2 * (1 - t) * (1 - t)) / 2);
}
/**
* 获取值范围
*/
public float[] getValueRange() {
if (keyframes.isEmpty()) {
return new float[]{defaultValue, defaultValue};
}
float min = Float.MAX_VALUE;
float max = Float.MIN_VALUE;
for (Keyframe kf : keyframes) {
min = Math.min(min, kf.getValue());
max = Math.max(max, kf.getValue());
}
return new float[]{min, max};
}
/**
* 创建曲线深拷贝
*/
public AnimationCurve copy() {
AnimationCurve copy = new AnimationCurve(parameterId, defaultValue);
for (Keyframe kf : this.keyframes) {
copy.keyframes.add(kf.copy());
}
return copy;
}
// Getter方法
public String getParameterId() { return parameterId; }
public List<Keyframe> getKeyframes() { return Collections.unmodifiableList(keyframes); }
public float getDefaultValue() { return defaultValue; }
}
/**
* 关键帧类
*/
public static class Keyframe {
private final float time;
private final float value;
private final InterpolationType interpolation;
public Keyframe(float time, float value, InterpolationType interpolation) {
this.time = time;
this.value = value;
this.interpolation = interpolation;
}
public Keyframe copy() {
return new Keyframe(time, value, interpolation);
}
// Getter方法
public float getTime() { return time; }
public float getValue() { return value; }
public InterpolationType getInterpolation() { return interpolation; }
@Override
public String toString() {
return String.format("Keyframe{time=%.2f, value=%.2f, interpolation=%s}",
time, value, interpolation);
}
}
/**
* 事件标记类
*/
public static class AnimationEventMarker {
private final String name;
private final float time;
private final Runnable action;
private boolean triggered;
public AnimationEventMarker(String name, float time, Runnable action) {
this.name = name;
this.time = time;
this.action = action;
this.triggered = false;
}
public void trigger() {
if (!triggered && action != null) {
action.run();
triggered = true;
}
}
public void reset() {
triggered = false;
}
public AnimationEventMarker copy() {
return new AnimationEventMarker(name, time, action);
}
// Getter方法
public String getName() { return name; }
public float getTime() { return time; }
public Runnable getAction() { return action; }
public boolean isTriggered() { return triggered; }
@Override
public String toString() {
return String.format("EventMarker{name='%s', time=%.2f}", name, time);
}
}
/**
* 插值类型枚举
*/
public enum InterpolationType {
LINEAR, // 线性插值
STEP, // 步进插值
SMOOTH, // 平滑插值三次Hermite
EASE_IN, // 缓入
EASE_OUT, // 缓出
EASE_IN_OUT // 缓入缓出
}
// ==================== Getter/Setter ====================
public String getName() { return name; }
public UUID getUuid() { return uuid; }
public float getDuration() { return duration; }
public void setDuration(float duration) {
this.duration = Math.max(0.0f, duration);
markModified();
}
public float getFramesPerSecond() { return framesPerSecond; }
public void setFramesPerSecond(float framesPerSecond) {
this.framesPerSecond = Math.max(1.0f, framesPerSecond);
markModified();
}
public boolean isLooping() { return looping; }
public void setLooping(boolean looping) { this.looping = looping; }
public Map<String, AnimationCurve> getCurves() {
return Collections.unmodifiableMap(curves);
}
public List<AnimationEventMarker> getEventMarkers() {
return Collections.unmodifiableList(eventMarkers);
}
public Map<String, Float> getDefaultValues() {
return Collections.unmodifiableMap(defaultValues);
}
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public long getCreationTime() { return creationTime; }
public long getLastModifiedTime() { return lastModifiedTime; }
public Map<String, String> getUserData() {
return Collections.unmodifiableMap(userData);
}
public void setUserData(Map<String, String> userData) {
this.userData = new ConcurrentHashMap<>(userData);
}
// ==================== Object 方法 ====================
@Override
public String toString() {
return String.format(
"AnimationClip{name='%s', duration=%.2f, curves=%d, events=%d, looping=%s}",
name, duration, curves.size(), eventMarkers.size(), looping
);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnimationClip that = (AnimationClip) o;
return uuid.equals(that.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
}

View File

@@ -0,0 +1,735 @@
package com.chuangzhou.vivid2D.render.model.util;
import com.chuangzhou.vivid2D.render.model.Model2D;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 动画层类用于管理2D模型的动画层和动画混合
* 支持多层动画叠加、权重控制、混合模式等高级功能
*
* @author tzdwindows 7
*/
public class AnimationLayer {
// ==================== 层属性 ====================
private final String name;
private final UUID uuid;
private float weight;
private boolean enabled;
private BlendMode blendMode;
private int priority;
// ==================== 动画数据 ====================
private final Map<String, AnimationTrack> tracks;
private final List<AnimationClip> clips;
private AnimationClip currentClip;
private float playbackSpeed;
private boolean looping;
// ==================== 状态管理 ====================
private float currentTime;
private boolean playing;
private boolean paused;
private Map<String, Float> parameterOverrides;
// ==================== 事件系统 ====================
private final List<AnimationEventListener> eventListeners;
private final Map<String, List<AnimationEvent>> events;
// ==================== 构造器 ====================
public AnimationLayer(String name) {
this(name, 1.0f);
}
public AnimationLayer(String name, float weight) {
this.name = name;
this.uuid = UUID.randomUUID();
this.weight = Math.max(0.0f, Math.min(1.0f, weight));
this.enabled = true;
this.blendMode = BlendMode.OVERRIDE;
this.priority = 0;
this.tracks = new ConcurrentHashMap<>();
this.clips = new ArrayList<>();
this.playbackSpeed = 1.0f;
this.looping = true;
this.currentTime = 0.0f;
this.playing = false;
this.paused = false;
this.parameterOverrides = new ConcurrentHashMap<>();
this.eventListeners = new ArrayList<>();
this.events = new ConcurrentHashMap<>();
}
// ==================== 轨道管理 ====================
/**
* 添加动画轨道
*/
public AnimationTrack addTrack(String parameterId) {
AnimationTrack track = new AnimationTrack(parameterId);
tracks.put(parameterId, track);
return track;
}
/**
* 获取动画轨道
*/
public AnimationTrack getTrack(String parameterId) {
return tracks.get(parameterId);
}
/**
* 移除动画轨道
*/
public boolean removeTrack(String parameterId) {
return tracks.remove(parameterId) != null;
}
/**
* 检查是否存在轨道
*/
public boolean hasTrack(String parameterId) {
return tracks.containsKey(parameterId);
}
// ==================== 剪辑管理 ====================
/**
* 添加动画剪辑
*/
public void addClip(AnimationClip clip) {
clips.add(clip);
}
/**
* 移除动画剪辑
*/
public boolean removeClip(AnimationClip clip) {
return clips.remove(clip);
}
/**
* 播放指定剪辑
*/
public void playClip(String clipName) {
for (AnimationClip clip : clips) {
if (clip.getName().equals(clipName)) {
playClip(clip);
return;
}
}
throw new IllegalArgumentException("Animation clip not found: " + clipName);
}
public void playClip(AnimationClip clip) {
this.currentClip = clip;
this.currentTime = 0.0f;
this.playing = true;
this.paused = false;
notifyAnimationStarted(clip);
}
/**
* 停止播放
*/
public void stop() {
this.playing = false;
this.paused = false;
this.currentTime = 0.0f;
if (currentClip != null) {
notifyAnimationStopped(currentClip);
}
}
/**
* 暂停播放
*/
public void pause() {
if (playing && !paused) {
paused = true;
notifyAnimationPaused(currentClip);
}
}
/**
* 恢复播放
*/
public void resume() {
if (playing && paused) {
paused = false;
notifyAnimationResumed(currentClip);
}
}
// ==================== 更新系统 ====================
/**
* 更新动画层
*/
public void update(float deltaTime, Model2D model) {
if (!enabled || weight <= 0.0f) {
return;
}
// 更新播放时间
if (playing && !paused) {
currentTime += deltaTime * playbackSpeed;
// 检查循环
if (currentClip != null && currentTime >= currentClip.getDuration()) {
if (looping) {
currentTime %= currentClip.getDuration();
notifyAnimationLooped(currentClip);
} else {
stop();
notifyAnimationCompleted(currentClip);
return;
}
}
// 检查事件
checkEvents();
}
// 应用动画
applyAnimation(model);
}
/**
* 应用动画到模型
*/
private void applyAnimation(Model2D model) {
if (currentClip != null) {
// 应用剪辑动画
applyClipAnimation(model);
} else {
// 应用轨道动画
applyTrackAnimation(model);
}
}
/**
* 应用剪辑动画
*/
private void applyClipAnimation(Model2D model) {
Map<String, Float> animatedValues = currentClip.sample(currentTime);
for (Map.Entry<String, Float> entry : animatedValues.entrySet()) {
String paramId = entry.getKey();
float value = entry.getValue();
// 应用权重和混合模式
float finalValue = applyBlending(model, paramId, value);
// 设置参数值
model.setParameterValue(paramId, finalValue);
}
}
/**
* 应用轨道动画
*/
private void applyTrackAnimation(Model2D model) {
for (AnimationTrack track : tracks.values()) {
if (track.isEnabled()) {
float value = track.sample(currentTime);
String paramId = track.getParameterId();
// 应用权重和混合模式
float finalValue = applyBlending(model, paramId, value);
// 设置参数值
model.setParameterValue(paramId, finalValue);
}
}
}
/**
* 应用混合模式
*/
private float applyBlending(Model2D model, String paramId, float newValue) {
float currentValue = model.getParameterValue(paramId);
float overrideValue = parameterOverrides.getOrDefault(paramId, Float.NaN);
if (!Float.isNaN(overrideValue)) {
return overrideValue;
}
switch (blendMode) {
case OVERRIDE:
return blendOverride(currentValue, newValue);
case ADDITIVE:
return blendAdditive(currentValue, newValue);
case MULTIPLICATIVE:
return blendMultiplicative(currentValue, newValue);
case AVERAGE:
return blendAverage(currentValue, newValue);
default:
return newValue;
}
}
private float blendOverride(float current, float target) {
return current + (target - current) * weight;
}
private float blendAdditive(float current, float target) {
return current + target * weight;
}
private float blendMultiplicative(float current, float target) {
return current * (1.0f + (target - 1.0f) * weight);
}
private float blendAverage(float current, float target) {
return (current * (1.0f - weight)) + (target * weight);
}
// ==================== 事件系统 ====================
/**
* 添加动画事件
*/
public void addEvent(String eventName, float time, Runnable action) {
AnimationEvent event = new AnimationEvent(eventName, time, action);
events.computeIfAbsent(eventName, k -> new ArrayList<>()).add(event);
}
/**
* 检查并触发事件
*/
private void checkEvents() {
if (currentClip == null) return;
for (List<AnimationEvent> eventList : events.values()) {
for (AnimationEvent event : eventList) {
if (!event.isTriggered() && currentTime >= event.getTime()) {
event.trigger();
notifyEventTriggered(event);
}
}
}
}
/**
* 重置所有事件状态
*/
public void resetEvents() {
for (List<AnimationEvent> eventList : events.values()) {
for (AnimationEvent event : eventList) {
event.reset();
}
}
}
// ==================== 参数覆盖 ====================
/**
* 设置参数覆盖值
*/
public void setParameterOverride(String parameterId, float value) {
parameterOverrides.put(parameterId, value);
}
/**
* 清除参数覆盖
*/
public void clearParameterOverride(String parameterId) {
parameterOverrides.remove(parameterId);
}
/**
* 清除所有参数覆盖
*/
public void clearAllOverrides() {
parameterOverrides.clear();
}
// ==================== 事件监听器 ====================
/**
* 添加事件监听器
*/
public void addEventListener(AnimationEventListener listener) {
eventListeners.add(listener);
}
/**
* 移除事件监听器
*/
public boolean removeEventListener(AnimationEventListener listener) {
return eventListeners.remove(listener);
}
private void notifyAnimationStarted(AnimationClip clip) {
for (AnimationEventListener listener : eventListeners) {
listener.onAnimationStarted(this, clip);
}
}
private void notifyAnimationStopped(AnimationClip clip) {
for (AnimationEventListener listener : eventListeners) {
listener.onAnimationStopped(this, clip);
}
}
private void notifyAnimationPaused(AnimationClip clip) {
for (AnimationEventListener listener : eventListeners) {
listener.onAnimationPaused(this, clip);
}
}
private void notifyAnimationResumed(AnimationClip clip) {
for (AnimationEventListener listener : eventListeners) {
listener.onAnimationResumed(this, clip);
}
}
private void notifyAnimationCompleted(AnimationClip clip) {
for (AnimationEventListener listener : eventListeners) {
listener.onAnimationCompleted(this, clip);
}
}
private void notifyAnimationLooped(AnimationClip clip) {
for (AnimationEventListener listener : eventListeners) {
listener.onAnimationLooped(this, clip);
}
}
private void notifyEventTriggered(AnimationEvent event) {
for (AnimationEventListener listener : eventListeners) {
listener.onEventTriggered(this, event);
}
}
// ==================== 工具方法 ====================
/**
* 获取当前播放进度0-1
*/
public float getProgress() {
if (currentClip == null || currentClip.getDuration() == 0) {
return 0.0f;
}
return currentTime / currentClip.getDuration();
}
/**
* 设置播放进度
*/
public void setProgress(float progress) {
if (currentClip != null) {
currentTime = progress * currentClip.getDuration();
}
}
/**
* 跳转到指定时间
*/
public void seek(float time) {
currentTime = Math.max(0.0f, time);
if (currentClip != null) {
currentTime = Math.min(currentTime, currentClip.getDuration());
}
}
/**
* 创建层的深拷贝
*/
public AnimationLayer copy() {
AnimationLayer copy = new AnimationLayer(name + "_copy", weight);
copy.enabled = this.enabled;
copy.blendMode = this.blendMode;
copy.priority = this.priority;
copy.playbackSpeed = this.playbackSpeed;
copy.looping = this.looping;
// 拷贝轨道
for (AnimationTrack track : this.tracks.values()) {
copy.tracks.put(track.getParameterId(), track.copy());
}
// 拷贝剪辑(引用,因为剪辑通常是共享的)
copy.clips.addAll(this.clips);
// 拷贝参数覆盖
copy.parameterOverrides.putAll(this.parameterOverrides);
return copy;
}
// ==================== Getter/Setter ====================
public String getName() { return name; }
public UUID getUuid() { return uuid; }
public float getWeight() { return weight; }
public void setWeight(float weight) {
this.weight = Math.max(0.0f, Math.min(1.0f, weight));
}
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public BlendMode getBlendMode() { return blendMode; }
public void setBlendMode(BlendMode blendMode) { this.blendMode = blendMode; }
public int getPriority() { return priority; }
public void setPriority(int priority) { this.priority = priority; }
public Map<String, AnimationTrack> getTracks() {
return Collections.unmodifiableMap(tracks);
}
public List<AnimationClip> getClips() {
return Collections.unmodifiableList(clips);
}
public AnimationClip getCurrentClip() { return currentClip; }
public float getPlaybackSpeed() { return playbackSpeed; }
public void setPlaybackSpeed(float playbackSpeed) {
this.playbackSpeed = Math.max(0.0f, playbackSpeed);
}
public boolean isLooping() { return looping; }
public void setLooping(boolean looping) { this.looping = looping; }
public float getCurrentTime() { return currentTime; }
public boolean isPlaying() { return playing; }
public boolean isPaused() { return paused; }
public Map<String, Float> getParameterOverrides() {
return Collections.unmodifiableMap(parameterOverrides);
}
// ==================== 枚举和内部类 ====================
/**
* 混合模式枚举
*/
public enum BlendMode {
OVERRIDE, // 覆盖混合
ADDITIVE, // 叠加混合
MULTIPLICATIVE, // 乘法混合
AVERAGE // 平均混合
}
/**
* 动画轨道类
*/
public static class AnimationTrack {
private final String parameterId;
private final List<Keyframe> keyframes;
private boolean enabled;
private InterpolationType interpolation;
public AnimationTrack(String parameterId) {
this.parameterId = parameterId;
this.keyframes = new ArrayList<>();
this.enabled = true;
this.interpolation = InterpolationType.LINEAR;
}
public void addKeyframe(float time, float value) {
addKeyframe(time, value, interpolation);
}
public void addKeyframe(float time, float value, InterpolationType interpolation) {
Keyframe keyframe = new Keyframe(time, value, interpolation);
// 按时间排序插入
int index = 0;
while (index < keyframes.size() && keyframes.get(index).getTime() < time) {
index++;
}
keyframes.add(index, keyframe);
}
public float sample(float time) {
if (keyframes.isEmpty()) {
return 0.0f;
}
// 在第一个关键帧之前
if (time <= keyframes.get(0).getTime()) {
return keyframes.get(0).getValue();
}
// 在最后一个关键帧之后
if (time >= keyframes.get(keyframes.size() - 1).getTime()) {
return keyframes.get(keyframes.size() - 1).getValue();
}
// 找到包围时间的关键帧
for (int i = 0; i < keyframes.size() - 1; i++) {
Keyframe kf1 = keyframes.get(i);
Keyframe kf2 = keyframes.get(i + 1);
if (time >= kf1.getTime() && time <= kf2.getTime()) {
return interpolate(kf1, kf2, time);
}
}
return 0.0f;
}
private float interpolate(Keyframe kf1, Keyframe kf2, float time) {
float t = (time - kf1.getTime()) / (kf2.getTime() - kf1.getTime());
switch (kf1.getInterpolation()) {
case LINEAR:
return kf1.getValue() + (kf2.getValue() - kf1.getValue()) * t;
case STEP:
return kf1.getValue();
case SMOOTH:
float t2 = t * t;
float t3 = t2 * t;
return kf1.getValue() * (2 * t3 - 3 * t2 + 1) +
kf2.getValue() * (-2 * t3 + 3 * t2);
case EASE_IN:
return kf1.getValue() + (kf2.getValue() - kf1.getValue()) * (t * t);
case EASE_OUT:
return kf1.getValue() + (kf2.getValue() - kf1.getValue()) * (1 - (1 - t) * (1 - t));
default:
return kf1.getValue();
}
}
public AnimationTrack copy() {
AnimationTrack copy = new AnimationTrack(parameterId);
copy.enabled = this.enabled;
copy.interpolation = this.interpolation;
for (Keyframe kf : this.keyframes) {
copy.keyframes.add(kf.copy());
}
return copy;
}
// Getter/Setter
public String getParameterId() { return parameterId; }
public List<Keyframe> getKeyframes() { return Collections.unmodifiableList(keyframes); }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public InterpolationType getInterpolation() { return interpolation; }
public void setInterpolation(InterpolationType interpolation) { this.interpolation = interpolation; }
}
/**
* 关键帧类
*/
public static class Keyframe {
private final float time;
private final float value;
private final InterpolationType interpolation;
public Keyframe(float time, float value, InterpolationType interpolation) {
this.time = time;
this.value = value;
this.interpolation = interpolation;
}
public Keyframe copy() {
return new Keyframe(time, value, interpolation);
}
// Getter
public float getTime() { return time; }
public float getValue() { return value; }
public InterpolationType getInterpolation() { return interpolation; }
}
/**
* 插值类型枚举
*/
public enum InterpolationType {
LINEAR, // 线性插值
STEP, // 步进插值
SMOOTH, // 平滑插值
EASE_IN, // 缓入
EASE_OUT // 缓出
}
/**
* 动画事件类
*/
public static class AnimationEvent {
private final String name;
private final float time;
private final Runnable action;
private boolean triggered;
public AnimationEvent(String name, float time, Runnable action) {
this.name = name;
this.time = time;
this.action = action;
this.triggered = false;
}
public void trigger() {
if (!triggered && action != null) {
action.run();
triggered = true;
}
}
public void reset() {
triggered = false;
}
// Getter
public String getName() { return name; }
public float getTime() { return time; }
public boolean isTriggered() { return triggered; }
}
/**
* 动画事件监听器接口
*/
public interface AnimationEventListener {
void onAnimationStarted(AnimationLayer layer, AnimationClip clip);
void onAnimationStopped(AnimationLayer layer, AnimationClip clip);
void onAnimationPaused(AnimationLayer layer, AnimationClip clip);
void onAnimationResumed(AnimationLayer layer, AnimationClip clip);
void onAnimationCompleted(AnimationLayer layer, AnimationClip clip);
void onAnimationLooped(AnimationLayer layer, AnimationClip clip);
void onEventTriggered(AnimationLayer layer, AnimationEvent event);
}
/**
* 简单的动画事件监听器适配器
*/
public static abstract class AnimationEventAdapter implements AnimationEventListener {
@Override public void onAnimationStarted(AnimationLayer layer, AnimationClip clip) {}
@Override public void onAnimationStopped(AnimationLayer layer, AnimationClip clip) {}
@Override public void onAnimationPaused(AnimationLayer layer, AnimationClip clip) {}
@Override public void onAnimationResumed(AnimationLayer layer, AnimationClip clip) {}
@Override public void onAnimationCompleted(AnimationLayer layer, AnimationClip clip) {}
@Override public void onAnimationLooped(AnimationLayer layer, AnimationClip clip) {}
@Override public void onEventTriggered(AnimationLayer layer, AnimationEvent event) {}
}
// ==================== Object 方法 ====================
@Override
public String toString() {
return "AnimationLayer{" +
"name='" + name + '\'' +
", weight=" + weight +
", enabled=" + enabled +
", blendMode=" + blendMode +
", tracks=" + tracks.size() +
", clips=" + clips.size() +
", playing=" + playing +
'}';
}
}

View File

@@ -0,0 +1,608 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Matrix3f;
import org.joml.Vector2f;
import java.util.Objects;
/**
* 2D边界框类用于表示和管理2D对象的轴对齐边界框(AABB)
* 支持变换、合并、相交检测等操作
*
* @author tzdwindows 7
*/
public class BoundingBox {
// ==================== 边界数据 ====================
private float minX;
private float minY;
private float maxX;
private float maxY;
// ==================== 状态标记 ====================
private boolean valid;
// ==================== 构造器 ====================
/**
* 创建未初始化的边界框
*/
public BoundingBox() {
reset();
}
/**
* 从最小/最大值创建边界框
*/
public BoundingBox(float minX, float minY, float maxX, float maxY) {
set(minX, minY, maxX, maxY);
}
/**
* 从两个点创建边界框
*/
public BoundingBox(Vector2f point1, Vector2f point2) {
set(point1, point2);
}
/**
* 拷贝构造器
*/
public BoundingBox(BoundingBox other) {
set(other);
}
/**
* 从点数组创建边界框
*/
public BoundingBox(Vector2f[] points) {
set(points);
}
/**
* 从顶点数组创建边界框 [x0, y0, x1, y1, ...]
*/
public BoundingBox(float[] vertices) {
set(vertices);
}
// ==================== 设置方法 ====================
/**
* 重置为无效状态
*/
public void reset() {
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
maxX = -Float.MAX_VALUE;
maxY = -Float.MAX_VALUE;
valid = false;
}
/**
* 设置边界值
*/
public void set(float minX, float minY, float maxX, float maxY) {
if (minX > maxX || minY > maxY) {
throw new IllegalArgumentException("Min values must be less than or equal to max values");
}
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
this.valid = true;
}
/**
* 从两个点设置边界框
*/
public void set(Vector2f point1, Vector2f point2) {
reset();
expand(point1);
expand(point2);
}
/**
* 从另一个边界框设置
*/
public void set(BoundingBox other) {
if (!other.isValid()) {
reset();
return;
}
this.minX = other.minX;
this.minY = other.minY;
this.maxX = other.maxX;
this.maxY = other.maxY;
this.valid = true;
}
/**
* 从点数组设置边界框
*/
public void set(Vector2f[] points) {
reset();
if (points != null) {
for (Vector2f point : points) {
if (point != null) {
expand(point);
}
}
}
}
/**
* 从顶点数组设置边界框 [x0, y0, x1, y1, ...]
*/
public void set(float[] vertices) {
reset();
if (vertices != null) {
if (vertices.length % 2 != 0) {
throw new IllegalArgumentException("Vertices array must have even length");
}
for (int i = 0; i < vertices.length; i += 2) {
expand(vertices[i], vertices[i + 1]);
}
}
}
// ==================== 扩展方法 ====================
/**
* 扩展边界框以包含点
*/
public void expand(float x, float y) {
if (!valid) {
minX = maxX = x;
minY = maxY = y;
valid = true;
} else {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
public void expand(Vector2f point) {
if (point != null) {
expand(point.x, point.y);
}
}
/**
* 扩展边界框以包含另一个边界框
*/
public void expand(BoundingBox other) {
if (!other.isValid()) {
return;
}
if (!valid) {
set(other);
} else {
minX = Math.min(minX, other.minX);
minY = Math.min(minY, other.minY);
maxX = Math.max(maxX, other.maxX);
maxY = Math.max(maxY, other.maxY);
}
}
/**
* 扩展边界框以包含点数组
*/
public void expand(Vector2f[] points) {
if (points != null) {
for (Vector2f point : points) {
if (point != null) {
expand(point);
}
}
}
}
/**
* 扩展边界框以包含顶点数组 [x0, y0, x1, y1, ...]
*/
public void expand(float[] vertices) {
if (vertices != null) {
if (vertices.length % 2 != 0) {
throw new IllegalArgumentException("Vertices array must have even length");
}
for (int i = 0; i < vertices.length; i += 2) {
expand(vertices[i], vertices[i + 1]);
}
}
}
// ==================== 变换方法 ====================
/**
* 应用矩阵变换到边界框
*/
public BoundingBox transform(Matrix3f matrix) {
if (!valid) {
return new BoundingBox();
}
// 变换边界框的四个角点
Vector2f[] corners = getCorners();
BoundingBox result = new BoundingBox();
for (Vector2f corner : corners) {
Vector2f transformed = Matrix3fUtils.transformPoint(matrix, corner);
result.expand(transformed);
}
return result;
}
/**
* 应用平移变换
*/
public BoundingBox translate(float dx, float dy) {
if (!valid) {
return new BoundingBox();
}
return new BoundingBox(
minX + dx, minY + dy,
maxX + dx, maxY + dy
);
}
public BoundingBox translate(Vector2f translation) {
return translate(translation.x, translation.y);
}
/**
* 应用缩放变换
*/
public BoundingBox scale(float sx, float sy) {
if (!valid) {
return new BoundingBox();
}
return new BoundingBox(
minX * sx, minY * sy,
maxX * sx, maxY * sy
);
}
public BoundingBox scale(float scale) {
return scale(scale, scale);
}
public BoundingBox scale(Vector2f scale) {
return scale(scale.x, scale.y);
}
// ==================== 几何计算 ====================
/**
* 获取边界框的四个角点
*/
public Vector2f[] getCorners() {
if (!valid) {
return new Vector2f[0];
}
return new Vector2f[] {
new Vector2f(minX, minY), // 左下
new Vector2f(maxX, minY), // 右下
new Vector2f(maxX, maxY), // 右上
new Vector2f(minX, maxY) // 左上
};
}
/**
* 获取边界框中心点
*/
public Vector2f getCenter() {
if (!valid) {
return new Vector2f();
}
return new Vector2f(
(minX + maxX) * 0.5f,
(minY + maxY) * 0.5f
);
}
/**
* 获取边界框尺寸
*/
public Vector2f getSize() {
if (!valid) {
return new Vector2f();
}
return new Vector2f(getWidth(), getHeight());
}
/**
* 获取边界框半尺寸(半径)
*/
public Vector2f getHalfSize() {
if (!valid) {
return new Vector2f();
}
return new Vector2f(getWidth() * 0.5f, getHeight() * 0.5f);
}
/**
* 计算边界框面积
*/
public float getArea() {
if (!valid) {
return 0.0f;
}
return getWidth() * getHeight();
}
/**
* 计算边界框周长
*/
public float getPerimeter() {
if (!valid) {
return 0.0f;
}
return 2.0f * (getWidth() + getHeight());
}
// ==================== 相交检测 ====================
/**
* 检查是否包含点
*/
public boolean contains(float x, float y) {
if (!valid) {
return false;
}
return x >= minX && x <= maxX && y >= minY && y <= maxY;
}
public boolean contains(Vector2f point) {
if (point == null) return false;
return contains(point.x, point.y);
}
/**
* 检查是否完全包含另一个边界框
*/
public boolean contains(BoundingBox other) {
if (!valid || !other.isValid()) {
return false;
}
return other.minX >= minX && other.maxX <= maxX &&
other.minY >= minY && other.maxY <= maxY;
}
/**
* 检查是否与另一个边界框相交
*/
public boolean intersects(BoundingBox other) {
if (!valid || !other.isValid()) {
return false;
}
return !(other.maxX < minX || other.minX > maxX ||
other.maxY < minY || other.minY > maxY);
}
/**
* 计算与另一个边界框的交集
*/
public BoundingBox intersection(BoundingBox other) {
if (!intersects(other)) {
return new BoundingBox(); // 返回无效边界框
}
return new BoundingBox(
Math.max(minX, other.minX),
Math.max(minY, other.minY),
Math.min(maxX, other.maxX),
Math.min(maxY, other.maxY)
);
}
/**
* 计算与另一个边界框的并集
*/
public BoundingBox union(BoundingBox other) {
BoundingBox result = new BoundingBox(this);
result.expand(other);
return result;
}
/**
* 计算两个边界框的合并边界框
*/
public static BoundingBox merge(BoundingBox box1, BoundingBox box2) {
return box1.union(box2);
}
// ==================== 工具方法 ====================
/**
* 对边界框进行膨胀(扩展固定距离)
*/
public BoundingBox inflate(float amount) {
return inflate(amount, amount);
}
public BoundingBox inflate(float dx, float dy) {
if (!valid) {
return new BoundingBox();
}
return new BoundingBox(
minX - dx, minY - dy,
maxX + dx, maxY + dy
);
}
/**
* 对边界框进行收缩(缩小固定距离)
*/
public BoundingBox deflate(float amount) {
return deflate(amount, amount);
}
public BoundingBox deflate(float dx, float dy) {
if (!valid) {
return new BoundingBox();
}
float newMinX = minX + dx;
float newMinY = minY + dy;
float newMaxX = maxX - dx;
float newMaxY = maxY - dy;
// 检查收缩后是否仍然有效
if (newMinX > newMaxX || newMinY > newMaxY) {
return new BoundingBox(); // 返回无效边界框
}
return new BoundingBox(newMinX, newMinY, newMaxX, newMaxY);
}
/**
* 将边界框对齐到网格
*/
public BoundingBox alignToGrid(float gridSize) {
if (!valid) {
return new BoundingBox();
}
float alignedMinX = (float) Math.floor(minX / gridSize) * gridSize;
float alignedMinY = (float) Math.floor(minY / gridSize) * gridSize;
float alignedMaxX = (float) Math.ceil(maxX / gridSize) * gridSize;
float alignedMaxY = (float) Math.ceil(maxY / gridSize) * gridSize;
return new BoundingBox(alignedMinX, alignedMinY, alignedMaxX, alignedMaxY);
}
/**
* 计算到点的最近距离
*/
public float distanceTo(float x, float y) {
if (!valid) {
return Float.MAX_VALUE;
}
if (contains(x, y)) {
return 0.0f;
}
float dx = Math.max(Math.max(minX - x, 0), x - maxX);
float dy = Math.max(Math.max(minY - y, 0), y - maxY);
return (float) Math.sqrt(dx * dx + dy * dy);
}
public float distanceTo(Vector2f point) {
if (point == null) return Float.MAX_VALUE;
return distanceTo(point.x, point.y);
}
// ==================== Getter方法 ====================
public float getMinX() { return minX; }
public float getMinY() { return minY; }
public float getMaxX() { return maxX; }
public float getMaxY() { return maxY; }
public float getWidth() {
return valid ? maxX - minX : 0.0f;
}
public float getHeight() {
return valid ? maxY - minY : 0.0f;
}
public float getLeft() { return minX; }
public float getRight() { return maxX; }
public float getBottom() { return minY; }
public float getTop() { return maxY; }
public boolean isValid() { return valid; }
// ==================== 静态工厂方法 ====================
/**
* 从点数组创建边界框
*/
public static BoundingBox fromPoints(Vector2f[] points) {
return new BoundingBox(points);
}
/**
* 从顶点数组创建边界框
*/
public static BoundingBox fromVertices(float[] vertices) {
return new BoundingBox(vertices);
}
/**
* 创建包含所有边界框的合并边界框
*/
public static BoundingBox mergeAll(BoundingBox... boxes) {
BoundingBox result = new BoundingBox();
for (BoundingBox box : boxes) {
if (box != null && box.isValid()) {
result.expand(box);
}
}
return result;
}
// ==================== Object方法 ====================
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BoundingBox that = (BoundingBox) o;
if (valid != that.valid) return false;
if (!valid) return true; // 两个无效边界框视为相等
return Float.compare(that.minX, minX) == 0 &&
Float.compare(that.minY, minY) == 0 &&
Float.compare(that.maxX, maxX) == 0 &&
Float.compare(that.maxY, maxY) == 0;
}
@Override
public int hashCode() {
if (!valid) {
return Objects.hash(valid);
}
return Objects.hash(minX, minY, maxX, maxY, valid);
}
@Override
public String toString() {
if (!valid) {
return "BoundingBox{INVALID}";
}
return String.format("BoundingBox{min=(%.2f, %.2f), max=(%.2f, %.2f), size=(%.2f, %.2f)}",
minX, minY, maxX, maxY, getWidth(), getHeight());
}
/**
* 创建边界框的深拷贝
*/
public BoundingBox copy() {
return new BoundingBox(this);
}
}

View File

@@ -0,0 +1,269 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import java.util.*;
/**
* 2D网格变形器基类
* 支持顶点变形、参数驱动动画等特性
*
* @author tzdwindows 7
*/
public abstract class Deformer {
// ==================== 基础属性 ====================
protected String name;
protected String id;
protected boolean enabled = true;
protected float weight = 1.0f;
// ==================== 驱动参数 ====================
protected final Set<String> drivenParameters;
protected final Map<String, Float> parameterValues;
// ==================== 变形范围 ====================
protected DeformationRange range;
protected BlendMode blendMode = BlendMode.REPLACE;
// ==================== 构造器 ====================
public Deformer() {
this("unnamed");
}
public Deformer(String name) {
this.name = name;
this.id = UUID.randomUUID().toString();
this.drivenParameters = new HashSet<>();
this.parameterValues = new HashMap<>();
this.range = new DeformationRange();
}
// ==================== 抽象方法 ====================
/**
* 应用变形到指定网格
*/
public abstract void applyToMesh(Mesh2D mesh);
/**
* 应用参数值到变形器
*/
public abstract void apply(float value);
/**
* 重置变形器状态
*/
public abstract void reset();
// ==================== 参数驱动系统 ====================
/**
* 检查是否由指定参数驱动
*/
public boolean isDrivenBy(String paramId) {
return drivenParameters.contains(paramId);
}
/**
* 添加驱动参数
*/
public void addDrivenParameter(String paramId) {
drivenParameters.add(paramId);
}
/**
* 移除驱动参数
*/
public void removeDrivenParameter(String paramId) {
drivenParameters.remove(paramId);
parameterValues.remove(paramId);
}
/**
* 设置参数值
*/
public void setParameterValue(String paramId, float value) {
if (drivenParameters.contains(paramId)) {
parameterValues.put(paramId, value);
}
}
/**
* 获取参数值
*/
public float getParameterValue(String paramId) {
return parameterValues.getOrDefault(paramId, 0.0f);
}
/**
* 应用所有参数到变形器
*/
public void applyAllParameters() {
for (Map.Entry<String, Float> entry : parameterValues.entrySet()) {
apply(entry.getValue());
}
}
// ==================== 工具方法 ====================
/**
* 计算变形权重(考虑全局权重和范围衰减)
*/
protected float computeDeformationWeight(float x, float y) {
if (!enabled || weight <= 0.0f) {
return 0.0f;
}
float rangeWeight = range.computeWeight(x, y);
return weight * rangeWeight;
}
/**
* 混合顶点位置
*/
protected void blendVertexPosition(float[] vertices, int vertexIndex,
float originalX, float originalY,
float deformedX, float deformedY, float weight) {
if (weight <= 0.0f) {
return; // 保持原位置
}
int baseIndex = vertexIndex * 2;
if (weight >= 1.0f) {
vertices[baseIndex] = deformedX;
vertices[baseIndex + 1] = deformedY;
return;
}
switch (blendMode) {
case ADDITIVE:
vertices[baseIndex] += (deformedX - originalX) * weight;
vertices[baseIndex + 1] += (deformedY - originalY) * weight;
break;
case MULTIPLY:
vertices[baseIndex] *= (1.0f + (deformedX / originalX - 1.0f) * weight);
vertices[baseIndex + 1] *= (1.0f + (deformedY / originalY - 1.0f) * weight);
break;
case REPLACE:
default:
vertices[baseIndex] = originalX + (deformedX - originalX) * weight;
vertices[baseIndex + 1] = originalY + (deformedY - originalY) * weight;
break;
}
}
// ==================== Getter/Setter ====================
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = Math.max(0.0f, Math.min(1.0f, weight));
}
public Set<String> getDrivenParameters() {
return new HashSet<>(drivenParameters);
}
public DeformationRange getRange() {
return range;
}
public void setRange(DeformationRange range) {
this.range = range;
}
public BlendMode getBlendMode() {
return blendMode;
}
public void setBlendMode(BlendMode blendMode) {
this.blendMode = blendMode;
}
// ==================== 枚举和内部类 ====================
/**
* 变形混合模式
*/
public enum BlendMode {
REPLACE, // 替换原始位置
ADDITIVE, // 叠加变形
MULTIPLY // 乘法变形
}
/**
* 变形范围控制
*/
public static class DeformationRange {
private Vector2f center = new Vector2f(0, 0);
private float radius = 100.0f;
private float innerRadius = 0.0f;
private float falloff = 2.0f;
public DeformationRange() {}
public DeformationRange(Vector2f center, float radius) {
this.center.set(center);
this.radius = radius;
}
/**
* 计算顶点在变形范围内的权重
*/
public float computeWeight(float x, float y) {
float dx = x - center.x;
float dy = y - center.y;
float distance = (float) Math.sqrt(dx * dx + dy * dy);
if (distance <= innerRadius) {
return 1.0f;
}
if (distance >= radius) {
return 0.0f;
}
// 使用平滑衰减函数
float normalized = (distance - innerRadius) / (radius - innerRadius);
return (float) Math.pow(1.0f - normalized, falloff);
}
// Getter/Setter
public Vector2f getCenter() { return new Vector2f(center); }
public void setCenter(Vector2f center) { this.center.set(center); }
public void setCenter(float x, float y) { this.center.set(x, y); }
public float getRadius() { return radius; }
public void setRadius(float radius) { this.radius = radius; }
public float getInnerRadius() { return innerRadius; }
public void setInnerRadius(float innerRadius) { this.innerRadius = innerRadius; }
public float getFalloff() { return falloff; }
public void setFalloff(float falloff) { this.falloff = falloff; }
}
}

View File

@@ -0,0 +1,28 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Matrix3f;
import org.joml.Vector2f;
/**
* @author tzdwindows 7
*/
public class Matrix3fUtils {
public static Vector2f transformPoint(Matrix3f matrix, Vector2f point, Vector2f dest) {
float x = matrix.m00() * point.x + matrix.m01() * point.y + matrix.m02();
float y = matrix.m10() * point.x + matrix.m11() * point.y + matrix.m12();
return dest.set(x, y);
}
public static Vector2f transformPoint(Matrix3f matrix, Vector2f point) {
return transformPoint(matrix, point, new Vector2f());
}
public static Vector2f transformPointInverse(Matrix3f matrix, Vector2f point, Vector2f dest) {
Matrix3f inverse = new Matrix3f(matrix).invert();
return transformPoint(inverse, point, dest);
}
public static Vector2f transformPointInverse(Matrix3f matrix, Vector2f point) {
return transformPointInverse(matrix, point, new Vector2f());
}
}

View File

@@ -0,0 +1,616 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* 2D网格类用于存储和管理2D模型的几何数据
* 支持顶点、UV坐标、索引和变形操作
*
* @author tzdwindows 7
*/
public class Mesh2D {
// ==================== 网格数据 ====================
private String name;
private float[] vertices; // 顶点数据 [x0, y0, x1, y1, ...]
private float[] uvs; // UV坐标 [u0, v0, u1, v1, ...]
private int[] indices; // 索引数据
private float[] originalVertices; // 原始顶点数据(用于变形恢复)
// ==================== 渲染属性 ====================
private Texture texture;
private boolean visible = true;
private int drawMode = TRIANGLES; // 绘制模式
private int vaoId = -1;
private int vboId = -1;
private int eboId = -1;
private int indexCount = 0;
private boolean uploaded = false;
// ==================== 状态管理 ====================
private boolean dirty = true; // 数据是否已修改
private BoundingBox bounds;
private boolean boundsDirty = true;
// ==================== 常量 ====================
public static final int POINTS = 0;
public static final int LINES = 1;
public static final int LINE_STRIP = 2;
public static final int TRIANGLES = 3;
public static final int TRIANGLE_STRIP = 4;
public static final int TRIANGLE_FAN = 5;
// ==================== 构造器 ====================
public Mesh2D() {
this("unnamed");
}
public Mesh2D(String name) {
this.name = name;
this.vertices = new float[0];
this.uvs = new float[0];
this.indices = new int[0];
this.bounds = new BoundingBox();
}
public Mesh2D(String name, float[] vertices, float[] uvs, int[] indices) {
this(name);
setMeshData(vertices, uvs, indices);
}
// ==================== 网格数据设置 ====================
/**
* 设置网格数据
*/
public void setMeshData(float[] vertices, float[] uvs, int[] indices) {
if (vertices.length % 2 != 0) {
throw new IllegalArgumentException("Vertices array must have even length (x,y pairs)");
}
if (uvs.length % 2 != 0) {
throw new IllegalArgumentException("UVs array must have even length (u,v pairs)");
}
if (vertices.length / 2 != uvs.length / 2) {
throw new IllegalArgumentException("Vertices and UVs must have same number of points");
}
this.vertices = vertices.clone();
this.uvs = uvs.clone();
this.indices = indices.clone();
this.originalVertices = vertices.clone();
markDirty();
}
/**
* 创建矩形网格
*/
public static Mesh2D createQuad(String name, float width, float height) {
float hw = width / 2.0f;
float hh = height / 2.0f;
float[] vertices = {
-hw, -hh, // 左下
hw, -hh, // 右下
hw, hh, // 右上
-hw, hh // 左上
};
float[] uvs = {
0.0f, 1.0f, // 左下
1.0f, 1.0f, // 右下
1.0f, 0.0f, // 右上
0.0f, 0.0f // 左上
};
int[] indices = {
0, 1, 2, // 第一个三角形
0, 2, 3 // 第二个三角形
};
return new Mesh2D(name, vertices, uvs, indices);
}
/**
* 创建圆形网格
*/
public static Mesh2D createCircle(String name, float radius, int segments) {
if (segments < 3) {
segments = 3;
}
int vertexCount = segments + 1; // 中心点 + 边缘点
float[] vertices = new float[vertexCount * 2];
float[] uvs = new float[vertexCount * 2];
int[] indices = new int[segments * 3];
// 中心点 (索引0)
vertices[0] = 0.0f;
vertices[1] = 0.0f;
uvs[0] = 0.5f;
uvs[1] = 0.5f;
// 边缘点
float angleStep = (float) (2.0f * Math.PI / segments);
for (int i = 0; i < segments; i++) {
float angle = i * angleStep;
int vertexIndex = (i + 1) * 2;
vertices[vertexIndex] = (float) Math.cos(angle) * radius;
vertices[vertexIndex + 1] = (float) Math.sin(angle) * radius;
uvs[vertexIndex] = (float) (Math.cos(angle) * 0.5f + 0.5f);
uvs[vertexIndex + 1] = (float) (Math.sin(angle) * 0.5f + 0.5f);
// 三角形索引
int triangleIndex = i * 3;
indices[triangleIndex] = 0; // 中心点
indices[triangleIndex + 1] = i + 1;
indices[triangleIndex + 2] = (i + 1) % segments + 1;
}
return new Mesh2D(name, vertices, uvs, indices);
}
// ==================== 顶点操作 ====================
/**
* 获取顶点数量
*/
public int getVertexCount() {
return vertices.length / 2;
}
/**
* 获取顶点位置
*/
public Vector2f getVertex(int index, Vector2f dest) {
if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
}
int baseIndex = index * 2;
return dest.set(vertices[baseIndex], vertices[baseIndex + 1]);
}
public Vector2f getVertex(int index) {
return getVertex(index, new Vector2f());
}
/**
* 设置顶点位置
*/
public void setVertex(int index, float x, float y) {
if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
}
int baseIndex = index * 2;
vertices[baseIndex] = x;
vertices[baseIndex + 1] = y;
markDirty();
}
public void setVertex(int index, Vector2f position) {
setVertex(index, position.x, position.y);
}
/**
* 获取UV坐标
*/
public Vector2f getUV(int index, Vector2f dest) {
if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("UV index out of bounds: " + index);
}
int baseIndex = index * 2;
return dest.set(uvs[baseIndex], uvs[baseIndex + 1]);
}
/**
* 设置UV坐标
*/
public void setUV(int index, float u, float v) {
if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("UV index out of bounds: " + index);
}
int baseIndex = index * 2;
uvs[baseIndex] = u;
uvs[baseIndex + 1] = v;
markDirty();
}
// ==================== 变形支持 ====================
/**
* 重置为原始顶点数据
*/
public void resetToOriginal() {
if (originalVertices != null && originalVertices.length == vertices.length) {
System.arraycopy(originalVertices, 0, vertices, 0, vertices.length);
markDirty();
}
}
/**
* 保存当前顶点为原始数据
*/
public void saveAsOriginal() {
originalVertices = vertices.clone();
}
/**
* 应用变形到所有顶点
*/
public void transformVertices(VertexTransformer transformer) {
for (int i = 0; i < getVertexCount(); i++) {
Vector2f vertex = getVertex(i);
transformer.transform(vertex, i);
setVertex(i, vertex);
}
markDirty();
}
/**
* 顶点变换器接口
*/
public interface VertexTransformer {
void transform(Vector2f vertex, int index);
}
// ==================== 边界计算 ====================
/**
* 更新边界框
*/
public void updateBounds() {
bounds.reset();
for (int i = 0; i < vertices.length; i += 2) {
bounds.expand(vertices[i], vertices[i + 1]);
}
boundsDirty = false;
}
/**
* 获取边界框
*/
public BoundingBox getBounds() {
if (boundsDirty) {
updateBounds();
}
return bounds;
}
/**
* 检查点是否在网格内(使用边界框近似)
*/
public boolean containsPoint(float x, float y) {
BoundingBox b = getBounds();
return x >= b.getMinX() && x <= b.getMaxX() && y >= b.getMinY() && y <= b.getMaxY();
}
public boolean containsPoint(Vector2f point) {
return containsPoint(point.x, point.y);
}
// ==================== 缓冲区支持 ====================
/**
* 获取顶点缓冲区数据
*/
public FloatBuffer getVertexBuffer(FloatBuffer buffer) {
if (buffer == null || buffer.capacity() < vertices.length) {
throw new IllegalArgumentException("Buffer is null or too small");
}
buffer.clear();
buffer.put(vertices);
buffer.flip();
return buffer;
}
/**
* 获取UV缓冲区数据
*/
public FloatBuffer getUVBuffer(FloatBuffer buffer) {
if (buffer == null || buffer.capacity() < uvs.length) {
throw new IllegalArgumentException("Buffer is null or too small");
}
buffer.clear();
buffer.put(uvs);
buffer.flip();
return buffer;
}
/**
* 获取索引缓冲区数据
*/
public IntBuffer getIndexBuffer(IntBuffer buffer) {
if (buffer == null || buffer.capacity() < indices.length) {
throw new IllegalArgumentException("Buffer is null or too small");
}
buffer.clear();
buffer.put(indices);
buffer.flip();
return buffer;
}
/**
* 获取交错的顶点+UV数据用于VBO
*/
public FloatBuffer getInterleavedBuffer(FloatBuffer buffer) {
int vertexCount = getVertexCount();
int floatCount = vertexCount * 4; // 每个顶点x, y, u, v
if (buffer == null || buffer.capacity() < floatCount) {
throw new IllegalArgumentException("Buffer is null or too small");
}
buffer.clear();
for (int i = 0; i < vertexCount; i++) {
buffer.put(vertices[i * 2]); // x
buffer.put(vertices[i * 2 + 1]); // y
buffer.put(uvs[i * 2]); // u
buffer.put(uvs[i * 2 + 1]); // v
}
buffer.flip();
return buffer;
}
// ==================== 状态管理 ====================
/**
* 标记数据已修改
*/
public void markDirty() {
deleteGPU();
this.dirty = true;
this.boundsDirty = true;
}
/**
* 清除脏标记
*/
public void markClean() {
this.dirty = false;
}
/**
* 检查数据是否已修改
*/
public boolean isDirty() {
return dirty;
}
/**
* 将网格数据上传到 GPU生成 VAO/VBO/EBO
*/
public void uploadToGPU() {
if (uploaded) return;
// 组织 interleaved buffer (x,y,u,v)
int vertexCount = getVertexCount();
int floatCount = vertexCount * 4; // x,y,u,v
FloatBuffer interleaved = MemoryUtil.memAllocFloat(floatCount);
try {
getInterleavedBuffer(interleaved);
IntBuffer ib = MemoryUtil.memAllocInt(indices.length);
try {
getIndexBuffer(ib);
vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);
vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, interleaved, GL15.GL_STATIC_DRAW);
eboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ib, GL15.GL_STATIC_DRAW);
int stride = 4 * Float.BYTES; // x,y,u,v
// position attrib (location 0) -> vec2
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, stride, 0);
// uv attrib (location 1) -> vec2
GL20.glEnableVertexAttribArray(1);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, stride, 2 * Float.BYTES);
// unbind VAO (keep EBO bound to VAO on unbind)
GL30.glBindVertexArray(0);
indexCount = indices.length;
uploaded = true;
markClean();
} finally {
MemoryUtil.memFree(ib);
}
} finally {
MemoryUtil.memFree(interleaved);
// unbind array buffer
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
/**
* 绘制网格(会在第一次绘制时自动上传到 GPU
*/
public void draw() {
if (!visible) return;
if (indices == null || indices.length == 0) return;
if (!uploaded) {
uploadToGPU();
}
if (texture != null) {
// 假设 Texture 提供 bind()/unbind() 方法
texture.bind();
}
GL30.glBindVertexArray(vaoId);
GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL11.GL_UNSIGNED_INT, 0);
GL30.glBindVertexArray(0);
if (texture != null) {
texture.unbind();
}
}
/**
* 从 GPU 删除本网格相关的 VAO/VBO/EBO
*/
public void deleteGPU() {
if (!uploaded) return;
// 禁用属性并删除缓冲
try {
GL30.glBindVertexArray(0);
if (vboId != -1) {
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboId);
vboId = -1;
}
if (eboId != -1) {
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(eboId);
eboId = -1;
}
if (vaoId != -1) {
GL30.glDeleteVertexArrays(vaoId);
vaoId = -1;
}
} catch (Exception ignored) {
// 在某些上下文销毁阶段 GL 调用可能不可用
} finally {
uploaded = false;
}
}
// ==================== Getter/Setter ====================
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float[] getVertices() {
return vertices.clone();
}
public float[] getUVs() {
return uvs.clone();
}
public int[] getIndices() {
return indices.clone();
}
public Texture getTexture() {
return texture;
}
public void setTexture(Texture texture) {
this.texture = texture;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public int getDrawMode() {
return drawMode;
}
public void setDrawMode(int drawMode) {
if (drawMode < POINTS || drawMode > TRIANGLE_FAN) {
throw new IllegalArgumentException("Invalid draw mode: " + drawMode);
}
this.drawMode = drawMode;
}
public int getIndexCount() {
return indices.length;
}
// ==================== 工具方法 ====================
/**
* 创建网格的深拷贝
*/
public Mesh2D copy() {
Mesh2D copy = new Mesh2D(name + "_copy");
copy.setMeshData(vertices, uvs, indices);
copy.texture = texture;
copy.visible = visible;
copy.drawMode = drawMode;
return copy;
}
/**
* 获取绘制模式字符串
*/
public String getDrawModeString() {
switch (drawMode) {
case POINTS: return "POINTS";
case LINES: return "LINES";
case LINE_STRIP: return "LINE_STRIP";
case TRIANGLES: return "TRIANGLES";
case TRIANGLE_STRIP: return "TRIANGLE_STRIP";
case TRIANGLE_FAN: return "TRIANGLE_FAN";
default: return "UNKNOWN";
}
}
// ==================== Object 方法 ====================
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Mesh2D mesh2D = (Mesh2D) o;
return visible == mesh2D.visible &&
drawMode == mesh2D.drawMode &&
Objects.equals(name, mesh2D.name) &&
Objects.deepEquals(vertices, mesh2D.vertices) &&
Objects.deepEquals(uvs, mesh2D.uvs) &&
Objects.deepEquals(indices, mesh2D.indices);
}
@Override
public int hashCode() {
return Objects.hash(name,
java.util.Arrays.hashCode(vertices),
java.util.Arrays.hashCode(uvs),
java.util.Arrays.hashCode(indices),
visible, drawMode);
}
@Override
public String toString() {
return "Mesh2D{" +
"name='" + name + '\'' +
", vertices=" + getVertexCount() +
", indices=" + indices.length +
", visible=" + visible +
", drawMode=" + getDrawModeString() +
", bounds=" + getBounds() +
'}';
}
}

View File

@@ -0,0 +1,627 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import java.io.Serializable;
import java.util.*;
/**
* 模型元数据类
* 用于存储模型的描述性信息、创建信息、版本信息等
*
* @author tzdwindows 7
*/
public class ModelMetadata implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
// ==================== 基础信息 ====================
private String name;
private String version;
private UUID uuid;
private String description;
// ==================== 创建信息 ====================
private String author;
private String creator;
private String copyright;
private String license;
private long creationTime;
private long lastModifiedTime;
// ==================== 技术信息 ====================
private String fileFormatVersion;
private int vertexCount;
private int polygonCount;
private int textureCount;
private int parameterCount;
private int partCount;
// ==================== 渲染设置 ====================
private Vector2f pivotPoint;
private float unitsPerMeter;
private boolean visibleInScene;
// ==================== 用户数据 ====================
private Map<String, String> userProperties;
private List<String> tags;
// ==================== 构造器 ====================
public ModelMetadata() {
this("unnamed", "1.0.0");
}
public ModelMetadata(String name) {
this(name, "1.0.0");
}
public ModelMetadata(String name, String version) {
this.name = name;
this.version = version;
this.uuid = UUID.randomUUID();
this.creationTime = System.currentTimeMillis();
this.lastModifiedTime = creationTime;
// 初始化默认值
this.pivotPoint = new Vector2f();
this.unitsPerMeter = 100.0f;
this.visibleInScene = true;
this.userProperties = new HashMap<>();
this.tags = new ArrayList<>();
}
// ==================== 基础信息方法 ====================
/**
* 验证元数据的基本完整性
*/
public boolean isValid() {
return name != null && !name.trim().isEmpty() &&
version != null && !version.trim().isEmpty() &&
uuid != null;
}
/**
* 获取模型的显示名称
*/
public String getDisplayName() {
if (name != null && !name.trim().isEmpty()) {
return name;
}
return "Unnamed Model";
}
/**
* 获取完整的版本信息
*/
public String getFullVersion() {
if (fileFormatVersion != null) {
return version + " (Format: " + fileFormatVersion + ")";
}
return version;
}
// ==================== 时间管理 ====================
/**
* 标记为已修改
*/
public void markModified() {
this.lastModifiedTime = System.currentTimeMillis();
}
/**
* 获取模型年龄(以天为单位)
*/
public long getAgeInDays() {
long currentTime = System.currentTimeMillis();
long ageMillis = currentTime - creationTime;
return ageMillis / (1000 * 60 * 60 * 24);
}
/**
* 获取最后修改后的时间(以小时为单位)
*/
public long getHoursSinceLastModified() {
long currentTime = System.currentTimeMillis();
long diffMillis = currentTime - lastModifiedTime;
return diffMillis / (1000 * 60 * 60);
}
// ==================== 标签管理 ====================
/**
* 添加标签
*/
public void addTag(String tag) {
if (tag != null && !tag.trim().isEmpty() && !tags.contains(tag)) {
tags.add(tag);
markModified();
}
}
/**
* 移除标签
*/
public boolean removeTag(String tag) {
boolean removed = tags.remove(tag);
if (removed) {
markModified();
}
return removed;
}
/**
* 检查是否包含标签
*/
public boolean hasTag(String tag) {
return tags.contains(tag);
}
/**
* 检查是否包含任何指定的标签
*/
public boolean hasAnyTag(String... searchTags) {
for (String tag : searchTags) {
if (tags.contains(tag)) {
return true;
}
}
return false;
}
/**
* 检查是否包含所有指定的标签
*/
public boolean hasAllTags(String... searchTags) {
for (String tag : searchTags) {
if (!tags.contains(tag)) {
return false;
}
}
return true;
}
// ==================== 用户属性管理 ====================
/**
* 设置用户属性
*/
public void setProperty(String key, String value) {
if (key != null && !key.trim().isEmpty()) {
userProperties.put(key, value);
markModified();
}
}
/**
* 获取用户属性
*/
public String getProperty(String key) {
return userProperties.get(key);
}
/**
* 获取用户属性,如果不存在则返回默认值
*/
public String getProperty(String key, String defaultValue) {
return userProperties.getOrDefault(key, defaultValue);
}
/**
* 移除用户属性
*/
public String removeProperty(String key) {
String removed = userProperties.remove(key);
if (removed != null) {
markModified();
}
return removed;
}
/**
* 检查是否存在属性
*/
public boolean hasProperty(String key) {
return userProperties.containsKey(key);
}
// ==================== 统计信息方法 ====================
/**
* 更新统计信息
*/
public void updateStatistics(int vertexCount, int polygonCount, int textureCount,
int parameterCount, int partCount) {
this.vertexCount = vertexCount;
this.polygonCount = polygonCount;
this.textureCount = textureCount;
this.parameterCount = parameterCount;
this.partCount = partCount;
markModified();
}
/**
* 获取模型复杂度评级
*/
public ComplexityRating getComplexityRating() {
int totalComplexity = vertexCount + (polygonCount * 10) +
(textureCount * 100) + (parameterCount * 5) +
(partCount * 20);
if (totalComplexity < 1000) {
return ComplexityRating.VERY_SIMPLE;
} else if (totalComplexity < 5000) {
return ComplexityRating.SIMPLE;
} else if (totalComplexity < 20000) {
return ComplexityRating.MEDIUM;
} else if (totalComplexity < 50000) {
return ComplexityRating.COMPLEX;
} else {
return ComplexityRating.VERY_COMPLEX;
}
}
/**
* 获取估计的文件大小(字节)
*/
public long getEstimatedFileSize() {
// 粗略估算:顶点数据 + 纹理数据 + 其他开销
long vertexDataSize = (long) vertexCount * 8 * 2; // 每个顶点8字节float x,y2份原始+变形)
long textureDataSize = (long) textureCount * 1024 * 1024; // 假设每个纹理1MB
long otherDataSize = (long) (parameterCount * 16 + partCount * 64 + polygonCount * 12);
return vertexDataSize + textureDataSize + otherDataSize + 1024; // +1KB元数据
}
// ==================== 工具方法 ====================
/**
* 创建深拷贝
*/
@Override
public ModelMetadata clone() {
try {
ModelMetadata clone = (ModelMetadata) super.clone();
// 深拷贝可变对象
clone.pivotPoint = new Vector2f(this.pivotPoint);
clone.userProperties = new HashMap<>(this.userProperties);
clone.tags = new ArrayList<>(this.tags);
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError("Clone should be supported", e);
}
}
/**
* 创建带有新名称的拷贝
*/
public ModelMetadata copyWithName(String newName) {
ModelMetadata copy = clone();
copy.name = newName;
copy.uuid = UUID.randomUUID();
copy.creationTime = System.currentTimeMillis();
copy.lastModifiedTime = copy.creationTime;
return copy;
}
/**
* 合并另一个元数据(主要用于模型合并)
*/
public void merge(ModelMetadata other) {
if (other == null) return;
// 合并描述
if (this.description == null || this.description.isEmpty()) {
this.description = other.description;
} else if (other.description != null && !other.description.isEmpty()) {
this.description += "; " + other.description;
}
// 合并作者信息
if (this.author == null || this.author.isEmpty()) {
this.author = other.author;
} else if (other.author != null && !other.author.isEmpty()) {
this.author += ", " + other.author;
}
// 合并标签(去重)
for (String tag : other.tags) {
if (!this.tags.contains(tag)) {
this.tags.add(tag);
}
}
// 合并用户属性(不覆盖现有属性)
for (Map.Entry<String, String> entry : other.userProperties.entrySet()) {
if (!this.userProperties.containsKey(entry.getKey())) {
this.userProperties.put(entry.getKey(), entry.getValue());
}
}
markModified();
}
/**
* 转换为简化的信息映射
*/
public Map<String, Object> toInfoMap() {
Map<String, Object> info = new LinkedHashMap<>();
info.put("name", name);
info.put("version", version);
info.put("uuid", uuid.toString());
info.put("author", author != null ? author : "Unknown");
info.put("description", description != null ? description : "No description");
info.put("creationTime", new Date(creationTime));
info.put("lastModifiedTime", new Date(lastModifiedTime));
info.put("vertexCount", vertexCount);
info.put("polygonCount", polygonCount);
info.put("textureCount", textureCount);
info.put("parameterCount", parameterCount);
info.put("partCount", partCount);
info.put("complexity", getComplexityRating().toString());
info.put("tags", String.join(", ", tags));
return info;
}
// ==================== 枚举和内部类 ====================
/**
* 模型复杂度评级
*/
public enum ComplexityRating {
VERY_SIMPLE("非常简单", "适合初学者"),
SIMPLE("简单", "基础模型"),
MEDIUM("中等", "标准模型"),
COMPLEX("复杂", "高级模型"),
VERY_COMPLEX("非常复杂", "专业级模型");
private final String displayName;
private final String description;
ComplexityRating(String displayName, String description) {
this.displayName = displayName;
this.description = description;
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return displayName + " (" + description + ")";
}
}
// ==================== Getter/Setter ====================
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
markModified();
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
markModified();
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
markModified();
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
markModified();
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
markModified();
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
markModified();
}
public String getCopyright() {
return copyright;
}
public void setCopyright(String copyright) {
this.copyright = copyright;
markModified();
}
public String getLicense() {
return license;
}
public void setLicense(String license) {
this.license = license;
markModified();
}
public long getCreationTime() {
return creationTime;
}
public void setCreationTime(long creationTime) {
this.creationTime = creationTime;
// 不标记修改,因为创建时间通常不应该改变
}
public long getLastModifiedTime() {
return lastModifiedTime;
}
public void setLastModifiedTime(long lastModifiedTime) {
this.lastModifiedTime = lastModifiedTime;
// 不标记修改,避免循环调用
}
public String getFileFormatVersion() {
return fileFormatVersion;
}
public void setFileFormatVersion(String fileFormatVersion) {
this.fileFormatVersion = fileFormatVersion;
markModified();
}
public int getVertexCount() {
return vertexCount;
}
public void setVertexCount(int vertexCount) {
this.vertexCount = vertexCount;
markModified();
}
public int getPolygonCount() {
return polygonCount;
}
public void setPolygonCount(int polygonCount) {
this.polygonCount = polygonCount;
markModified();
}
public int getTextureCount() {
return textureCount;
}
public void setTextureCount(int textureCount) {
this.textureCount = textureCount;
markModified();
}
public int getParameterCount() {
return parameterCount;
}
public void setParameterCount(int parameterCount) {
this.parameterCount = parameterCount;
markModified();
}
public int getPartCount() {
return partCount;
}
public void setPartCount(int partCount) {
this.partCount = partCount;
markModified();
}
public Vector2f getPivotPoint() {
return pivotPoint;
}
public void setPivotPoint(Vector2f pivotPoint) {
this.pivotPoint = pivotPoint;
markModified();
}
public float getUnitsPerMeter() {
return unitsPerMeter;
}
public void setUnitsPerMeter(float unitsPerMeter) {
this.unitsPerMeter = unitsPerMeter;
markModified();
}
public boolean isVisibleInScene() {
return visibleInScene;
}
public void setVisibleInScene(boolean visibleInScene) {
this.visibleInScene = visibleInScene;
markModified();
}
public Map<String, String> getUserProperties() {
return Collections.unmodifiableMap(userProperties);
}
public void setUserProperties(Map<String, String> userProperties) {
this.userProperties = new HashMap<>(userProperties);
markModified();
}
public List<String> getTags() {
return Collections.unmodifiableList(tags);
}
public void setTags(List<String> tags) {
this.tags = new ArrayList<>(tags);
markModified();
}
// ==================== Object 方法 ====================
@Override
public String toString() {
return "ModelMetadata{" +
"name='" + name + '\'' +
", version='" + version + '\'' +
", uuid=" + uuid +
", author='" + author + '\'' +
", vertexCount=" + vertexCount +
", polygonCount=" + polygonCount +
", tags=" + tags.size() +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModelMetadata that = (ModelMetadata) o;
return creationTime == that.creationTime &&
Objects.equals(uuid, that.uuid) &&
Objects.equals(name, that.name) &&
Objects.equals(version, that.version);
}
@Override
public int hashCode() {
return Objects.hash(name, version, uuid, creationTime);
}
}

View File

@@ -0,0 +1,4 @@
package com.chuangzhou.vivid2D.render.model.util;
public class ModelPose {
}

View File

@@ -0,0 +1,942 @@
package com.chuangzhou.vivid2D.render.model.util;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import org.joml.Vector2f;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 2D物理系统用于处理模型的物理模拟
* 支持弹簧系统、碰撞检测、重力等物理效果
*
* @author tzdwindows 7
*/
public class PhysicsSystem {
// ==================== 物理参数 ====================
private final Vector2f gravity;
private float airResistance;
private float timeScale;
private boolean enabled;
// ==================== 物理组件 ====================
private final Map<String, PhysicsParticle> particles;
private final List<PhysicsSpring> springs;
private final List<PhysicsConstraint> constraints;
private final List<PhysicsCollider> colliders;
// ==================== 状态管理 ====================
private boolean initialized;
private long lastUpdateTime;
private float accumulatedTime;
private final int maxSubSteps;
private final float fixedTimeStep;
// ==================== 性能统计 ====================
private int updateCount;
private float averageUpdateTime;
// ==================== 构造器 ====================
public PhysicsSystem() {
this.gravity = new Vector2f(0.0f, -98.0f); // 默认重力
this.airResistance = 0.1f;
this.timeScale = 1.0f;
this.enabled = true;
this.particles = new ConcurrentHashMap<>();
this.springs = new ArrayList<>();
this.constraints = new ArrayList<>();
this.colliders = new ArrayList<>();
this.initialized = false;
this.lastUpdateTime = System.nanoTime();
this.accumulatedTime = 0.0f;
this.maxSubSteps = 5;
this.fixedTimeStep = 1.0f / 60.0f; // 60 FPS物理更新
this.updateCount = 0;
this.averageUpdateTime = 0.0f;
}
// ==================== 初始化方法 ====================
/**
* 初始化物理系统
*/
public void initialize() {
if (initialized) return;
reset();
initialized = true;
}
/**
* 重置物理系统
*/
public void reset() {
particles.clear();
springs.clear();
constraints.clear();
colliders.clear();
lastUpdateTime = System.nanoTime();
accumulatedTime = 0.0f;
updateCount = 0;
averageUpdateTime = 0.0f;
}
// ==================== 粒子管理 ====================
/**
* 添加物理粒子
*/
public PhysicsParticle addParticle(String id, Vector2f position, float mass) {
PhysicsParticle particle = new PhysicsParticle(id, position, mass);
particles.put(id, particle);
return particle;
}
/**
* 从模型部件创建粒子
*/
public PhysicsParticle addParticleFromModelPart(String id, ModelPart part, float mass) {
Vector2f position = part.getPosition();
PhysicsParticle particle = addParticle(id, position, mass);
particle.setUserData(part);
return particle;
}
/**
* 移除粒子
*/
public boolean removeParticle(String id) {
// 移除相关的弹簧和约束
springs.removeIf(spring ->
spring.getParticleA().getId().equals(id) ||
spring.getParticleB().getId().equals(id));
constraints.removeIf(constraint ->
constraint.getParticle().getId().equals(id));
return particles.remove(id) != null;
}
/**
* 获取粒子
*/
public PhysicsParticle getParticle(String id) {
return particles.get(id);
}
// ==================== 弹簧管理 ====================
/**
* 添加弹簧
*/
public PhysicsSpring addSpring(String id, PhysicsParticle a, PhysicsParticle b,
float restLength, float stiffness, float damping) {
PhysicsSpring spring = new PhysicsSpring(id, a, b, restLength, stiffness, damping);
springs.add(spring);
return spring;
}
/**
* 添加弹簧(自动计算自然长度)
*/
public PhysicsSpring addSpring(String id, PhysicsParticle a, PhysicsParticle b,
float stiffness, float damping) {
float restLength = a.getPosition().distance(b.getPosition());
return addSpring(id, a, b, restLength, stiffness, damping);
}
/**
* 移除弹簧
*/
public boolean removeSpring(PhysicsSpring spring) {
return springs.remove(spring);
}
// ==================== 约束管理 ====================
/**
* 添加位置约束
*/
public PhysicsConstraint addPositionConstraint(PhysicsParticle particle, Vector2f targetPosition) {
PhysicsConstraint constraint = new PositionConstraint(particle, targetPosition);
constraints.add(constraint);
return constraint;
}
/**
* 添加距离约束
*/
public PhysicsConstraint addDistanceConstraint(PhysicsParticle particle, PhysicsParticle target,
float maxDistance) {
PhysicsConstraint constraint = new DistanceConstraint(particle, target, maxDistance);
constraints.add(constraint);
return constraint;
}
/**
* 移除约束
*/
public boolean removeConstraint(PhysicsConstraint constraint) {
return constraints.remove(constraint);
}
// ==================== 碰撞管理 ====================
/**
* 添加圆形碰撞体
*/
public PhysicsCollider addCircleCollider(String id, Vector2f center, float radius) {
PhysicsCollider collider = new CircleCollider(id, center, radius);
colliders.add(collider);
return collider;
}
/**
* 添加矩形碰撞体
*/
public PhysicsCollider addRectangleCollider(String id, Vector2f center, float width, float height) {
PhysicsCollider collider = new RectangleCollider(id, center, width, height);
colliders.add(collider);
return collider;
}
/**
* 移除碰撞体
*/
public boolean removeCollider(PhysicsCollider collider) {
return colliders.remove(collider);
}
// ==================== 更新系统 ====================
/**
* 更新物理系统
*/
public void update(float deltaTime, Model2D model) {
if (!enabled || !initialized) return;
long startTime = System.nanoTime();
// 应用时间缩放
float scaledDeltaTime = deltaTime * timeScale;
accumulatedTime += scaledDeltaTime;
// 固定时间步长更新
int numSubSteps = 0;
while (accumulatedTime >= fixedTimeStep && numSubSteps < maxSubSteps) {
updatePhysics(fixedTimeStep);
accumulatedTime -= fixedTimeStep;
numSubSteps++;
}
// 应用物理结果到模型
applyToModel(model);
// 更新性能统计
updatePerformanceStats(System.nanoTime() - startTime);
}
/**
* 物理模拟更新
*/
private void updatePhysics(float deltaTime) {
// 清除所有力
for (PhysicsParticle particle : particles.values()) {
particle.clearForces();
}
// 应用重力
applyGravity();
// 应用弹簧力
for (PhysicsSpring spring : springs) {
spring.applyForce(deltaTime);
}
// 更新粒子运动
for (PhysicsParticle particle : particles.values()) {
if (particle.isMovable()) {
particle.update(deltaTime);
}
}
// 应用约束
for (PhysicsConstraint constraint : constraints) {
constraint.apply(deltaTime);
}
// 处理碰撞
handleCollisions(deltaTime);
// 应用空气阻力
applyAirResistance(deltaTime);
}
/**
* 应用重力
*/
private void applyGravity() {
for (PhysicsParticle particle : particles.values()) {
if (particle.isMovable() && particle.isAffectedByGravity()) {
Vector2f gravityForce = new Vector2f(gravity).mul(particle.getMass());
particle.addForce(gravityForce);
}
}
}
/**
* 应用空气阻力
*/
private void applyAirResistance(float deltaTime) {
for (PhysicsParticle particle : particles.values()) {
if (particle.isMovable()) {
Vector2f velocity = particle.getVelocity();
Vector2f dragForce = new Vector2f(velocity).mul(-airResistance);
particle.addForce(dragForce);
}
}
}
/**
* 处理碰撞
*/
private void handleCollisions(float deltaTime) {
// 粒子与碰撞体碰撞
for (PhysicsParticle particle : particles.values()) {
if (!particle.isMovable()) continue;
for (PhysicsCollider collider : colliders) {
if (collider.isEnabled() && collider.collidesWith(particle)) {
collider.resolveCollision(particle, deltaTime);
}
}
}
// 粒子间碰撞(简单实现)
handleParticleCollisions(deltaTime);
}
/**
* 处理粒子间碰撞
*/
private void handleParticleCollisions(float deltaTime) {
List<PhysicsParticle> particleList = new ArrayList<>(particles.values());
for (int i = 0; i < particleList.size(); i++) {
PhysicsParticle p1 = particleList.get(i);
if (!p1.isMovable()) continue;
for (int j = i + 1; j < particleList.size(); j++) {
PhysicsParticle p2 = particleList.get(j);
if (!p2.isMovable()) continue;
// 简单圆形碰撞检测
Vector2f delta = new Vector2f(p2.getPosition()).sub(p1.getPosition());
float distance = delta.length();
float minDistance = p1.getRadius() + p2.getRadius();
if (distance < minDistance && distance > 0.001f) {
// 碰撞响应
Vector2f normal = new Vector2f(delta).div(distance);
float overlap = minDistance - distance;
// 分离粒子
Vector2f separation = new Vector2f(normal).mul(overlap * 0.5f);
p1.getPosition().sub(separation);
p2.getPosition().add(separation);
// 简单的速度响应
Vector2f relativeVelocity = new Vector2f(p2.getVelocity()).sub(p1.getVelocity());
float velocityAlongNormal = relativeVelocity.dot(normal);
if (velocityAlongNormal > 0) continue; // 已经分离
float restitution = 0.5f; // 弹性系数
float impulseMagnitude = -(1 + restitution) * velocityAlongNormal;
impulseMagnitude /= p1.getInverseMass() + p2.getInverseMass();
Vector2f impulse = new Vector2f(normal).mul(impulseMagnitude);
p1.getVelocity().sub(new Vector2f(impulse).mul(p1.getInverseMass()));
p2.getVelocity().add(new Vector2f(impulse).mul(p2.getInverseMass()));
}
}
}
}
/**
* 应用物理结果到模型
*/
private void applyToModel(Model2D model) {
for (PhysicsParticle particle : particles.values()) {
Object userData = particle.getUserData();
if (userData instanceof ModelPart) {
ModelPart part = (ModelPart) userData;
part.setPosition(particle.getPosition());
// 可选:根据速度设置旋转
if (particle.getVelocity().lengthSquared() > 0.1f) {
float angle = (float) Math.atan2(particle.getVelocity().y, particle.getVelocity().x);
part.setRotation(angle);
}
}
}
}
// ==================== 性能统计 ====================
/**
* 更新性能统计
*/
private void updatePerformanceStats(long nanoTime) {
float millis = nanoTime / 1_000_000.0f;
// 指数移动平均
if (updateCount == 0) {
averageUpdateTime = millis;
} else {
averageUpdateTime = averageUpdateTime * 0.95f + millis * 0.05f;
}
updateCount++;
}
/**
* 获取性能报告
*/
public PhysicsPerformanceReport getPerformanceReport() {
return new PhysicsPerformanceReport(
particles.size(),
springs.size(),
constraints.size(),
colliders.size(),
averageUpdateTime,
updateCount
);
}
// ==================== Getter/Setter ====================
public Vector2f getGravity() {
return new Vector2f(gravity);
}
public void setGravity(float x, float y) {
gravity.set(x, y);
}
public void setGravity(Vector2f gravity) {
this.gravity.set(gravity);
}
public float getAirResistance() {
return airResistance;
}
public void setAirResistance(float airResistance) {
this.airResistance = Math.max(0.0f, airResistance);
}
public float getTimeScale() {
return timeScale;
}
public void setTimeScale(float timeScale) {
this.timeScale = Math.max(0.0f, timeScale);
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isInitialized() {
return initialized;
}
public Map<String, PhysicsParticle> getParticles() {
return Collections.unmodifiableMap(particles);
}
public List<PhysicsSpring> getSprings() {
return Collections.unmodifiableList(springs);
}
public List<PhysicsConstraint> getConstraints() {
return Collections.unmodifiableList(constraints);
}
public List<PhysicsCollider> getColliders() {
return Collections.unmodifiableList(colliders);
}
/**
* 检查是否有活跃的物理效果
* 返回true表示当前有物理效果正在影响模型
*/
public boolean hasActivePhysics() {
if (!enabled || !initialized) {
return false;
}
// 检查是否有可移动的粒子
boolean hasMovableParticles = particles.values().stream()
.anyMatch(particle -> particle.isMovable() && particle.isAffectedByGravity());
if (!hasMovableParticles) {
return false;
}
// 检查是否有活跃的弹簧
boolean hasActiveSprings = springs.stream()
.anyMatch(spring -> spring.isEnabled() &&
(spring.getParticleA().isMovable() || spring.getParticleB().isMovable()));
// 检查粒子是否有显著的运动
boolean hasSignificantMotion = particles.values().stream()
.anyMatch(particle -> {
if (!particle.isMovable()) return false;
// 检查速度是否超过阈值
float speedSquared = particle.getVelocity().lengthSquared();
if (speedSquared > 0.1f) { // 速度阈值,可调整
return true;
}
// 检查位置是否显著变化(相对于前一帧)
Vector2f positionDelta = new Vector2f(particle.getPosition())
.sub(particle.getPreviousPosition()); // 现在可以正常使用了
float positionDeltaSquared = positionDelta.lengthSquared();
if (positionDeltaSquared > 0.001f) { // 位置变化阈值,可调整
return true;
}
return false;
});
// 检查是否有活跃的约束
boolean hasActiveConstraints = constraints.stream()
.anyMatch(constraint -> constraint.isEnabled() &&
constraint.getParticle().isMovable());
return hasActiveSprings || hasSignificantMotion || hasActiveConstraints;
}
// ==================== 内部类 ====================
/**
* 物理粒子类
*/
public static class PhysicsParticle {
private final String id;
private final Vector2f position;
private final Vector2f previousPosition;
private final Vector2f velocity;
private final Vector2f acceleration;
private final Vector2f forceAccumulator;
private final float mass;
private final float inverseMass;
private final float radius;
private boolean movable;
private boolean affectedByGravity;
private Object userData;
public PhysicsParticle(String id, Vector2f position, float mass) {
this.id = id;
this.position = new Vector2f(position);
this.previousPosition = new Vector2f(position);
this.velocity = new Vector2f();
this.acceleration = new Vector2f();
this.forceAccumulator = new Vector2f();
this.mass = Math.max(0.001f, mass);
this.inverseMass = 1.0f / this.mass;
this.radius = 2.0f; // 默认半径
this.movable = true;
this.affectedByGravity = true;
}
public Vector2f getPreviousPosition() {
return new Vector2f(previousPosition);
}
public void update(float deltaTime) {
if (!movable) return;
// Verlet 积分法
Vector2f temp = new Vector2f(position);
// 计算加速度: a = F / m
acceleration.set(forceAccumulator).mul(inverseMass);
// Verlet 位置更新: x_{n+1} = 2x_n - x_{n-1} + a * dt^2
position.set(2.0f * position.x - previousPosition.x + acceleration.x * deltaTime * deltaTime,
2.0f * position.y - previousPosition.y + acceleration.y * deltaTime * deltaTime);
previousPosition.set(temp);
// 更新速度(用于显示和其他计算)
velocity.set(position).sub(previousPosition).div(deltaTime);
}
public void addForce(Vector2f force) {
forceAccumulator.add(force);
}
public void clearForces() {
forceAccumulator.set(0.0f, 0.0f);
}
// Getter/Setter 方法
public String getId() { return id; }
public Vector2f getPosition() { return new Vector2f(position); }
public void setPosition(Vector2f position) { this.position.set(position); }
public Vector2f getVelocity() { return new Vector2f(velocity); }
public void setVelocity(Vector2f velocity) { this.velocity.set(velocity); }
public Vector2f getAcceleration() { return new Vector2f(acceleration); }
public float getMass() { return mass; }
public float getInverseMass() { return inverseMass; }
public float getRadius() { return radius; }
public void setRadius(float radius) { /* this.radius = radius; */ } // 注意:半径在构造后不可变
public boolean isMovable() { return movable; }
public void setMovable(boolean movable) { this.movable = movable; }
public boolean isAffectedByGravity() { return affectedByGravity; }
public void setAffectedByGravity(boolean affectedByGravity) { this.affectedByGravity = affectedByGravity; }
public Object getUserData() { return userData; }
public void setUserData(Object userData) { this.userData = userData; }
}
/**
* 物理弹簧类
*/
public static class PhysicsSpring {
private final String id;
private final PhysicsParticle particleA;
private final PhysicsParticle particleB;
private final float restLength;
private final float stiffness;
private final float damping;
private boolean enabled;
public PhysicsSpring(String id, PhysicsParticle a, PhysicsParticle b,
float restLength, float stiffness, float damping) {
this.id = id;
this.particleA = a;
this.particleB = b;
this.restLength = restLength;
this.stiffness = stiffness;
this.damping = damping;
this.enabled = true;
}
public void applyForce(float deltaTime) {
if (!enabled) return;
Vector2f delta = new Vector2f(particleB.getPosition()).sub(particleA.getPosition());
float currentLength = delta.length();
if (currentLength < 0.001f) return; // 避免除以零
// 胡克定律: F = -k * (currentLength - restLength)
float stretch = currentLength - restLength;
Vector2f springForce = new Vector2f(delta).normalize().mul(stiffness * stretch);
// 阻尼力: F_damp = -damping * relativeVelocity
Vector2f relativeVelocity = new Vector2f(particleB.getVelocity()).sub(particleA.getVelocity());
float velocityAlongSpring = relativeVelocity.dot(delta) / currentLength;
Vector2f dampingForce = new Vector2f(delta).normalize().mul(damping * velocityAlongSpring);
// 应用合力
Vector2f totalForce = new Vector2f(springForce).sub(dampingForce);
if (particleA.isMovable()) {
particleA.addForce(totalForce);
}
if (particleB.isMovable()) {
particleB.addForce(totalForce.negate());
}
}
// Getter/Setter 方法
public String getId() { return id; }
public PhysicsParticle getParticleA() { return particleA; }
public PhysicsParticle getParticleB() { return particleB; }
public float getRestLength() { return restLength; }
public float getStiffness() { return stiffness; }
public float getDamping() { return damping; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
}
/**
* 物理约束接口
*/
public interface PhysicsConstraint {
void apply(float deltaTime);
PhysicsParticle getParticle();
boolean isEnabled();
void setEnabled(boolean enabled);
}
/**
* 位置约束
*/
public static class PositionConstraint implements PhysicsConstraint {
private final PhysicsParticle particle;
private final Vector2f targetPosition;
private float strength;
private boolean enabled;
public PositionConstraint(PhysicsParticle particle, Vector2f targetPosition) {
this.particle = particle;
this.targetPosition = new Vector2f(targetPosition);
this.strength = 0.5f;
this.enabled = true;
}
@Override
public void apply(float deltaTime) {
if (!enabled || !particle.isMovable()) return;
Vector2f currentPos = particle.getPosition();
Vector2f delta = new Vector2f(targetPosition).sub(currentPos);
Vector2f correction = new Vector2f(delta).mul(strength);
particle.setPosition(new Vector2f(currentPos).add(correction));
}
// Getter/Setter 方法
@Override public PhysicsParticle getParticle() { return particle; }
@Override public boolean isEnabled() { return enabled; }
@Override public void setEnabled(boolean enabled) { this.enabled = enabled; }
public Vector2f getTargetPosition() { return new Vector2f(targetPosition); }
public void setTargetPosition(Vector2f targetPosition) { this.targetPosition.set(targetPosition); }
public float getStrength() { return strength; }
public void setStrength(float strength) { this.strength = Math.max(0.0f, Math.min(1.0f, strength)); }
}
/**
* 距离约束
*/
public static class DistanceConstraint implements PhysicsConstraint {
private final PhysicsParticle particle;
private final PhysicsParticle target;
private final float maxDistance;
private boolean enabled;
public DistanceConstraint(PhysicsParticle particle, PhysicsParticle target, float maxDistance) {
this.particle = particle;
this.target = target;
this.maxDistance = maxDistance;
this.enabled = true;
}
@Override
public void apply(float deltaTime) {
if (!enabled || !particle.isMovable()) return;
Vector2f delta = new Vector2f(particle.getPosition()).sub(target.getPosition());
float distance = delta.length();
if (distance > maxDistance) {
Vector2f correction = new Vector2f(delta).normalize().mul(distance - maxDistance);
particle.setPosition(new Vector2f(particle.getPosition()).sub(correction));
}
}
// Getter/Setter 方法
@Override public PhysicsParticle getParticle() { return particle; }
@Override public boolean isEnabled() { return enabled; }
@Override public void setEnabled(boolean enabled) { this.enabled = enabled; }
public PhysicsParticle getTarget() { return target; }
public float getMaxDistance() { return maxDistance; }
}
/**
* 物理碰撞体接口
*/
public interface PhysicsCollider {
boolean collidesWith(PhysicsParticle particle);
void resolveCollision(PhysicsParticle particle, float deltaTime);
String getId();
boolean isEnabled();
void setEnabled(boolean enabled);
}
/**
* 圆形碰撞体
*/
public static class CircleCollider implements PhysicsCollider {
private final String id;
private final Vector2f center;
private final float radius;
private boolean enabled;
public CircleCollider(String id, Vector2f center, float radius) {
this.id = id;
this.center = new Vector2f(center);
this.radius = radius;
this.enabled = true;
}
@Override
public boolean collidesWith(PhysicsParticle particle) {
float distance = particle.getPosition().distance(center);
return distance < (radius + particle.getRadius());
}
@Override
public void resolveCollision(PhysicsParticle particle, float deltaTime) {
Vector2f toParticle = new Vector2f(particle.getPosition()).sub(center);
float distance = toParticle.length();
float overlap = (radius + particle.getRadius()) - distance;
if (overlap > 0 && distance > 0.001f) {
// 分离
Vector2f normal = new Vector2f(toParticle).div(distance);
particle.getPosition().add(new Vector2f(normal).mul(overlap));
// 反弹
float dot = particle.getVelocity().dot(normal);
if (dot < 0) {
Vector2f reflection = new Vector2f(normal).mul(2.0f * dot);
particle.getVelocity().sub(reflection).mul(0.8f); // 能量损失
}
}
}
// Getter/Setter 方法
@Override public String getId() { return id; }
@Override public boolean isEnabled() { return enabled; }
@Override public void setEnabled(boolean enabled) { this.enabled = enabled; }
public Vector2f getCenter() { return new Vector2f(center); }
public void setCenter(Vector2f center) { this.center.set(center); }
public float getRadius() { return radius; }
}
/**
* 矩形碰撞体
*/
public static class RectangleCollider implements PhysicsCollider {
private final String id;
private final Vector2f center;
private final float width;
private final float height;
private boolean enabled;
public RectangleCollider(String id, Vector2f center, float width, float height) {
this.id = id;
this.center = new Vector2f(center);
this.width = width;
this.height = height;
this.enabled = true;
}
@Override
public boolean collidesWith(PhysicsParticle particle) {
Vector2f particlePos = particle.getPosition();
float left = center.x - width / 2;
float right = center.x + width / 2;
float bottom = center.y - height / 2;
float top = center.y + height / 2;
// 扩展边界考虑粒子半径
left -= particle.getRadius();
right += particle.getRadius();
bottom -= particle.getRadius();
top += particle.getRadius();
return particlePos.x >= left && particlePos.x <= right &&
particlePos.y >= bottom && particlePos.y <= top;
}
@Override
public void resolveCollision(PhysicsParticle particle, float deltaTime) {
Vector2f particlePos = particle.getPosition();
float left = center.x - width / 2;
float right = center.x + width / 2;
float bottom = center.y - height / 2;
float top = center.y + height / 2;
// 计算最近边界
float closestX = Math.max(left, Math.min(particlePos.x, right));
float closestY = Math.max(bottom, Math.min(particlePos.y, top));
Vector2f closestPoint = new Vector2f(closestX, closestY);
Vector2f normal = new Vector2f(particlePos).sub(closestPoint);
if (normal.lengthSquared() > 0.001f) {
normal.normalize();
// 分离粒子
float overlap = particle.getRadius() - normal.length();
if (overlap > 0) {
particle.getPosition().add(new Vector2f(normal).mul(overlap));
// 反弹
float dot = particle.getVelocity().dot(normal);
if (dot < 0) {
Vector2f reflection = new Vector2f(normal).mul(2.0f * dot);
particle.getVelocity().sub(reflection).mul(0.8f); // 能量损失
}
}
}
}
// Getter/Setter 方法
@Override public String getId() { return id; }
@Override public boolean isEnabled() { return enabled; }
@Override public void setEnabled(boolean enabled) { this.enabled = enabled; }
public Vector2f getCenter() { return new Vector2f(center); }
public void setCenter(Vector2f center) { this.center.set(center); }
public float getWidth() { return width; }
public float getHeight() { return height; }
}
/**
* 物理性能报告
*/
public static class PhysicsPerformanceReport {
private final int particleCount;
private final int springCount;
private final int constraintCount;
private final int colliderCount;
private final float averageUpdateTime;
private final int totalUpdates;
public PhysicsPerformanceReport(int particleCount, int springCount, int constraintCount,
int colliderCount, float averageUpdateTime, int totalUpdates) {
this.particleCount = particleCount;
this.springCount = springCount;
this.constraintCount = constraintCount;
this.colliderCount = colliderCount;
this.averageUpdateTime = averageUpdateTime;
this.totalUpdates = totalUpdates;
}
// Getter 方法
public int getParticleCount() { return particleCount; }
public int getSpringCount() { return springCount; }
public int getConstraintCount() { return constraintCount; }
public int getColliderCount() { return colliderCount; }
public float getAverageUpdateTime() { return averageUpdateTime; }
public int getTotalUpdates() { return totalUpdates; }
@Override
public String toString() {
return String.format(
"Physics Performance: %d particles, %d springs, %d constraints, %d colliders, " +
"Avg update: %.2fms, Total updates: %d",
particleCount, springCount, constraintCount, colliderCount,
averageUpdateTime, totalUpdates
);
}
}
}

View File

@@ -0,0 +1,654 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 纹理类,使用 LWJGL OpenGL API 实现完整的纹理管理
*
* @author tzdwindows 7
*/
public class Texture {
// ==================== 纹理属性 ====================
private final int textureId;
private final String name;
private final int width;
private final int height;
private final TextureFormat format;
private final TextureType type;
// ==================== 纹理参数 ====================
private TextureFilter minFilter = TextureFilter.LINEAR;
private TextureFilter magFilter = TextureFilter.LINEAR;
private TextureWrap wrapS = TextureWrap.CLAMP_TO_EDGE;
private TextureWrap wrapT = TextureWrap.CLAMP_TO_EDGE;
private boolean mipmapsEnabled = false;
private boolean textureCreated = false;
// ==================== 状态管理 ====================
private boolean disposed = false;
private final long creationTime;
// ==================== 静态管理 ====================
private static final Map<String, Texture> TEXTURE_CACHE = new HashMap<>();
private static boolean openGLChecked = false;
// ==================== 枚举定义 ====================
public enum TextureFormat {
RGB(3, GL11.GL_RGB, GL11.GL_RGB),
RGBA(4, GL11.GL_RGBA, GL11.GL_RGBA),
ALPHA(1, GL11.GL_ALPHA, GL11.GL_ALPHA),
LUMINANCE(1, GL11.GL_LUMINANCE, GL11.GL_LUMINANCE),
LUMINANCE_ALPHA(2, GL11.GL_LUMINANCE_ALPHA, GL11.GL_LUMINANCE_ALPHA),
RED(1, GL30.GL_RED, GL30.GL_RED),
RG(2, GL30.GL_RG, GL30.GL_RG);
private final int components;
private final int glInternalFormat;
private final int glFormat;
TextureFormat(int components, int glInternalFormat, int glFormat) {
this.components = components;
this.glInternalFormat = glInternalFormat;
this.glFormat = glFormat;
}
public int getComponents() { return components; }
public int getGLInternalFormat() { return glInternalFormat; }
public int getGLFormat() { return glFormat; }
}
public enum TextureType {
UNSIGNED_BYTE(GL11.GL_UNSIGNED_BYTE),
BYTE(GL11.GL_BYTE),
UNSIGNED_SHORT(GL11.GL_UNSIGNED_SHORT),
SHORT(GL11.GL_SHORT),
UNSIGNED_INT(GL11.GL_UNSIGNED_INT),
INT(GL11.GL_INT),
FLOAT(GL11.GL_FLOAT);
private final int glType;
TextureType(int glType) {
this.glType = glType;
}
public int getGLType() { return glType; }
}
public enum TextureFilter {
NEAREST(GL11.GL_NEAREST),
LINEAR(GL11.GL_LINEAR),
NEAREST_MIPMAP_NEAREST(GL11.GL_NEAREST_MIPMAP_NEAREST),
LINEAR_MIPMAP_NEAREST(GL11.GL_LINEAR_MIPMAP_NEAREST),
NEAREST_MIPMAP_LINEAR(GL11.GL_NEAREST_MIPMAP_LINEAR),
LINEAR_MIPMAP_LINEAR(GL11.GL_LINEAR_MIPMAP_LINEAR);
private final int glFilter;
TextureFilter(int glFilter) {
this.glFilter = glFilter;
}
public int getGLFilter() { return glFilter; }
}
public enum TextureWrap {
REPEAT(GL11.GL_REPEAT),
MIRRORED_REPEAT(GL14.GL_MIRRORED_REPEAT),
CLAMP_TO_EDGE(GL12.GL_CLAMP_TO_EDGE),
CLAMP_TO_BORDER(GL13.GL_CLAMP_TO_BORDER);
private final int glWrap;
TextureWrap(int glWrap) {
this.glWrap = glWrap;
}
public int getGLWrap() { return glWrap; }
}
// ==================== 构造器 ====================
public Texture(String name, int width, int height, TextureFormat format) {
this(name, width, height, format, TextureType.UNSIGNED_BYTE);
}
public Texture(String name, int width, int height, TextureFormat format, TextureType type) {
checkOpenGLCapabilities();
this.textureId = generateTextureId();
this.name = name;
this.width = width;
this.height = height;
this.format = format;
this.type = type;
this.creationTime = System.currentTimeMillis();
// 创建空的纹理对象
createTextureObject();
applyTextureParameters();
}
public Texture(String name, int width, int height, TextureFormat format, ByteBuffer pixelData) {
this(name, width, height, format);
uploadData(pixelData);
}
public Texture(String name, int width, int height, TextureFormat format, int[] pixelData) {
this(name, width, height, format);
uploadData(pixelData);
}
// ==================== OpenGL 能力检查 ====================
/**
* 检查 OpenGL 能力
*/
private static void checkOpenGLCapabilities() {
if (!openGLChecked) {
if (!GL.getCapabilities().OpenGL11) {
throw new RuntimeException("OpenGL 1.1 is required but not supported");
}
openGLChecked = true;
}
}
// ==================== 纹理数据管理 ====================
/**
* 创建纹理对象
*/
private void createTextureObject() {
if (textureCreated) return;
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
// 分配纹理存储 - 使用兼容性更好的方法
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format.getGLInternalFormat(),
width, height, 0, format.getGLFormat(), type.getGLType(),
(ByteBuffer) null);
textureCreated = true;
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
checkGLError("createTextureObject");
}
/**
* 上传字节缓冲区数据到纹理
*/
public void uploadData(ByteBuffer pixelData) {
if (disposed) {
throw new IllegalStateException("Cannot upload data to disposed texture: " + name);
}
if (pixelData == null) {
throw new IllegalArgumentException("Pixel data cannot be null");
}
int expectedSize = width * height * format.getComponents();
if (pixelData.remaining() < expectedSize) {
throw new IllegalArgumentException(
String.format("Pixel data buffer too small for texture dimensions. Expected %d, got %d",
expectedSize, pixelData.remaining()));
}
bind(0);
if (!textureCreated) {
createTextureObject();
}
// 上传纹理数据
GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, width, height,
format.getGLFormat(), type.getGLType(), pixelData);
// 检查OpenGL错误
checkGLError("glTexSubImage2D");
unbind();
}
/**
* 上传整数数组数据到纹理
*/
public void uploadData(int[] pixelData) {
if (pixelData == null) {
throw new IllegalArgumentException("Pixel data cannot be null");
}
if (pixelData.length < width * height) {
throw new IllegalArgumentException("Pixel data array too small for texture dimensions");
}
// 将int数组转换为ByteBuffer
ByteBuffer buffer = MemoryUtil.memAlloc(pixelData.length * 4);
buffer.asIntBuffer().put(pixelData);
buffer.position(0);
try {
uploadData(buffer);
} finally {
MemoryUtil.memFree(buffer);
}
}
/**
* 生成mipmaps
*/
public void generateMipmaps() {
if (disposed) {
throw new IllegalStateException("Cannot generate mipmaps for disposed texture: " + name);
}
if (!isPowerOfTwo(width) || !isPowerOfTwo(height)) {
System.err.println("Warning: Cannot generate mipmaps for non-power-of-two texture: " + name);
return;
}
bind(0);
// 重新创建纹理为可变纹理
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format.getGLInternalFormat(),
width, height, 0, format.getGLFormat(), type.getGLType(),
(ByteBuffer) null);
textureCreated = true;
// 生成mipmaps
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
// 检查OpenGL错误
checkGLError("glGenerateMipmap");
mipmapsEnabled = true;
// 更新过滤器以使用mipmaps
if (minFilter == TextureFilter.LINEAR) {
setMinFilter(TextureFilter.LINEAR_MIPMAP_LINEAR);
} else if (minFilter == TextureFilter.NEAREST) {
setMinFilter(TextureFilter.NEAREST_MIPMAP_NEAREST);
}
unbind();
}
// ==================== 纹理参数设置 ====================
/**
* 设置最小化过滤器
*/
public void setMinFilter(TextureFilter filter) {
if (this.minFilter != filter) {
this.minFilter = filter;
applyTextureParameters();
}
}
/**
* 设置最大化过滤器
*/
public void setMagFilter(TextureFilter filter) {
if (this.magFilter != filter) {
this.magFilter = filter;
applyTextureParameters();
}
}
/**
* 设置S轴包装模式
*/
public void setWrapS(TextureWrap wrap) {
if (this.wrapS != wrap) {
this.wrapS = wrap;
applyTextureParameters();
}
}
/**
* 设置T轴包装模式
*/
public void setWrapT(TextureWrap wrap) {
if (this.wrapT != wrap) {
this.wrapT = wrap;
applyTextureParameters();
}
}
/**
* 设置双向包装模式
*/
public void setWrap(TextureWrap wrap) {
setWrapS(wrap);
setWrapT(wrap);
}
/**
* 应用纹理参数到GPU
*/
public void applyTextureParameters() {
if (disposed) return;
bind(0);
// 设置纹理参数
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, minFilter.getGLFilter());
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, magFilter.getGLFilter());
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrapS.getGLWrap());
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrapT.getGLWrap());
// 检查OpenGL错误
checkGLError("glTexParameteri");
unbind();
}
// ==================== 绑定管理 ====================
/**
* 绑定纹理到指定纹理单元
*/
public void bind(int textureUnit) {
if (disposed) {
throw new IllegalStateException("Cannot bind disposed texture: " + name);
}
// 安全地激活纹理单元
if (textureUnit >= 0 && textureUnit < 32) { // 合理的纹理单元范围
try {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + textureUnit);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
checkGLError("glBindTexture");
} catch (Exception e) {
// 如果 GL13 不可用,回退到基本方法
System.err.println("Warning: GL13 not available, using fallback texture binding");
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
}
} else {
throw new IllegalArgumentException("Invalid texture unit: " + textureUnit);
}
}
/**
* 绑定纹理到默认纹理单元(0)
*/
public void bind() {
bind(0);
}
/**
* 解绑纹理
*/
public void unbind() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
// ==================== 资源管理 ====================
/**
* 释放纹理资源
*/
public void dispose() {
if (!disposed) {
try {
IntBuffer textures = MemoryUtil.memAllocInt(1);
textures.put(textureId);
textures.flip();
GL11.glDeleteTextures(textures);
MemoryUtil.memFree(textures);
} catch (Exception e) {
System.err.println("Error disposing texture: " + e.getMessage());
}
disposed = true;
TEXTURE_CACHE.values().removeIf(texture -> texture.textureId == this.textureId);
}
}
/**
* 检查纹理是否已释放
*/
public boolean isDisposed() {
return disposed;
}
// ==================== 静态工厂方法 ====================
/**
* 创建纯色纹理
*/
public static Texture createSolidColor(String name, int width, int height, int rgbaColor) {
int[] pixels = new int[width * height];
java.util.Arrays.fill(pixels, rgbaColor);
return new Texture(name, width, height, TextureFormat.RGBA, pixels);
}
/**
* 创建棋盘格纹理(用于调试)
*/
public static Texture createCheckerboard(String name, int width, int height, int tileSize,
int color1, int color2) {
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
boolean isColor1 = ((x / tileSize) + (y / tileSize)) % 2 == 0;
pixels[y * width + x] = isColor1 ? color1 : color2;
}
}
return new Texture(name, width, height, TextureFormat.RGBA, pixels);
}
/**
* 从缓存获取纹理,如果不存在则创建
*/
public static Texture getOrCreate(String name, int width, int height, TextureFormat format) {
return TEXTURE_CACHE.computeIfAbsent(name, k ->
new Texture(name, width, height, format));
}
// ==================== 工具方法 ====================
/**
* 获取纹理内存占用估算(字节)
*/
public long getEstimatedMemoryUsage() {
int bytesPerPixel;
switch (type) {
case UNSIGNED_BYTE:
case BYTE:
bytesPerPixel = format.getComponents();
break;
case UNSIGNED_SHORT:
case SHORT:
bytesPerPixel = format.getComponents() * 2;
break;
case UNSIGNED_INT:
case INT:
case FLOAT:
bytesPerPixel = format.getComponents() * 4;
break;
default:
bytesPerPixel = 4;
}
long baseMemory = (long) width * height * bytesPerPixel;
// 如果启用了mipmaps加上mipmaps的内存
if (mipmapsEnabled) {
return baseMemory * 4L / 3L; // mipmaps大约增加1/3内存
}
return baseMemory;
}
/**
* 检查尺寸是否为2的幂
*/
private boolean isPowerOfTwo(int value) {
return value > 0 && (value & (value - 1)) == 0;
}
/**
* 计算mipmap级别数量
*/
private int calculateMipmapLevels() {
return (int) Math.floor(Math.log(Math.max(width, height)) / Math.log(2)) + 1;
}
/**
* 生成纹理ID
*/
private int generateTextureId() {
try {
IntBuffer textures = MemoryUtil.memAllocInt(1);
GL11.glGenTextures(textures);
int textureId = textures.get(0);
MemoryUtil.memFree(textures);
if (textureId == 0) {
throw new RuntimeException("Failed to generate texture ID");
}
return textureId;
} catch (Exception e) {
throw new RuntimeException("Failed to generate texture: " + e.getMessage(), e);
}
}
/**
* 检查OpenGL错误
*/
private void checkGLError(String operation) {
int error = GL11.glGetError();
if (error != GL11.GL_NO_ERROR) {
String errorName = getGLErrorString(error);
System.err.println("OpenGL error during " + operation + ": " + errorName);
// 不再抛出异常,而是记录错误
}
}
/**
* 获取 OpenGL 错误字符串
*/
private String getGLErrorString(int error) {
switch (error) {
case GL11.GL_INVALID_ENUM: return "GL_INVALID_ENUM";
case GL11.GL_INVALID_VALUE: return "GL_INVALID_VALUE";
case GL11.GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
case GL11.GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
case GL11.GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW";
case GL11.GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW";
default: return "Unknown Error (0x" + Integer.toHexString(error) + ")";
}
}
// ==================== Getter方法 ====================
public int getTextureId() {
return textureId;
}
public String getName() {
return name;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public TextureFormat getFormat() {
return format;
}
public TextureType getType() {
return type;
}
public TextureFilter getMinFilter() {
return minFilter;
}
public TextureFilter getMagFilter() {
return magFilter;
}
public TextureWrap getWrapS() {
return wrapS;
}
public TextureWrap getWrapT() {
return wrapT;
}
public boolean isMipmapsEnabled() {
return mipmapsEnabled;
}
public long getCreationTime() {
return creationTime;
}
// ==================== Object方法 ====================
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Texture texture = (Texture) o;
return textureId == texture.textureId &&
width == texture.width &&
height == texture.height &&
disposed == texture.disposed &&
Objects.equals(name, texture.name) &&
format == texture.format &&
type == texture.type;
}
@Override
public int hashCode() {
return Objects.hash(textureId, name, width, height, format, type, disposed);
}
@Override
public String toString() {
return "Texture{" +
"id=" + textureId +
", name='" + name + '\'' +
", size=" + width + "x" + height +
", format=" + format +
", type=" + type +
", memory=" + getEstimatedMemoryUsage() + " bytes" +
", disposed=" + disposed +
'}';
}
// ==================== 静态清理方法 ====================
/**
* 清理所有缓存的纹理
*/
public static void cleanupAll() {
TEXTURE_CACHE.values().forEach(Texture::dispose);
TEXTURE_CACHE.clear();
}
}

View File

@@ -0,0 +1,287 @@
package com.chuangzhou.vivid2D.test;
import com.chuangzhou.vivid2D.render.ModelRender;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.Texture;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.Random;
/**
* 重写后的 ModelRender 测试示例:构造一个简单的人形(头、身体、左右手、左右腿)
* 便于验证层级变换与渲染是否正确。
*
* 注意:依赖你工程里已有的 Model2D / ModelPart / Mesh2D / Texture API。
*
* @author tzdwindows 7
*/
public class ModelRenderTest {
private static final int WINDOW_WIDTH = 800;
private static final int WINDOW_HEIGHT = 600;
private static final String WINDOW_TITLE = "Vivid2D ModelRender Test - Humanoid";
private long window;
private boolean running = true;
private Model2D testModel;
private Random random = new Random();
private float animationTime = 0f;
private boolean animate = true;
public static void main(String[] args) {
new ModelRenderTest().run();
}
public void run() {
try {
init();
loop();
} catch (Throwable t) {
t.printStackTrace();
} finally {
cleanup();
}
}
private void init() {
GLFWErrorCallback.createPrint(System.err).set();
if (!GLFW.glfwInit()) {
throw new IllegalStateException("Unable to initialize GLFW");
}
GLFW.glfwDefaultWindowHints();
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE);
window = GLFW.glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, MemoryUtil.NULL, MemoryUtil.NULL);
if (window == MemoryUtil.NULL) throw new RuntimeException("Failed to create GLFW window");
GLFWVidMode vidMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
GLFW.glfwSetWindowPos(window,
(vidMode.width() - WINDOW_WIDTH) / 2,
(vidMode.height() - WINDOW_HEIGHT) / 2);
GLFW.glfwSetKeyCallback(window, (wnd, key, scancode, action, mods) -> {
if (key == GLFW.GLFW_KEY_ESCAPE && action == GLFW.GLFW_RELEASE) running = false;
if (key == GLFW.GLFW_KEY_SPACE && action == GLFW.GLFW_RELEASE) {
animate = !animate;
System.out.println("Animation " + (animate ? "enabled" : "disabled"));
}
if (key == GLFW.GLFW_KEY_R && action == GLFW.GLFW_RELEASE) randomizeModel();
});
GLFW.glfwSetWindowSizeCallback(window, (wnd, w, h) -> ModelRender.setViewport(w, h));
GLFW.glfwMakeContextCurrent(window);
GLFW.glfwSwapInterval(1);
GLFW.glfwShowWindow(window);
GL.createCapabilities();
createTestModel();
ModelRender.initialize();
System.out.println("Test initialized successfully");
System.out.println("Controls: ESC exit | SPACE toggle anim | R randomize");
}
/**
* 构造一个简单的人形body 为根head、arms、legs 为 body 的子节点。
* 使用 createPart 保证与 Model2D 管理一致。
*/
private void createTestModel() {
testModel = new Model2D("Humanoid");
// body 放在屏幕中心
ModelPart body = testModel.createPart("body");
body.setPosition(400, 320);
// 身体网格:宽 80 高 120
Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 80, 120);
bodyMesh.setTexture(createSolidTexture(64, 128, 0xFF4A6AFF)); // 蓝衣
body.addMesh(bodyMesh);
// head相对于 body 在上方偏移
ModelPart head = testModel.createPart("head");
head.setPosition(0, -90); // 注意:如果 body 的坐标是屏幕位置,子部件的 position 是相对父节点(取决于你的实现);这里按常见习惯设负 y 向上
Mesh2D headMesh = Mesh2D.createQuad("head_mesh", 60, 60);
headMesh.setTexture(createHeadTexture());
head.addMesh(headMesh);
// left arm
ModelPart leftArm = testModel.createPart("left_arm");
leftArm.setPosition(-60, -20); // 在 body 左侧稍上位置
Mesh2D leftArmMesh = Mesh2D.createQuad("left_arm_mesh", 18, 90);
leftArmMesh.setTexture(createSolidTexture(16, 90, 0xFF6495ED)); // 手臂颜色
leftArm.addMesh(leftArmMesh);
// right arm
ModelPart rightArm = testModel.createPart("right_arm");
rightArm.setPosition(60, -20);
Mesh2D rightArmMesh = Mesh2D.createQuad("right_arm_mesh", 18, 90);
rightArmMesh.setTexture(createSolidTexture(16, 90, 0xFF6495ED));
rightArm.addMesh(rightArmMesh);
// left leg
ModelPart leftLeg = testModel.createPart("left_leg");
leftLeg.setPosition(-20, 90); // body 下方
Mesh2D leftLegMesh = Mesh2D.createQuad("left_leg_mesh", 20, 100);
leftLegMesh.setTexture(createSolidTexture(20, 100, 0xFF4169E1));
leftLeg.addMesh(leftLegMesh);
// right leg
ModelPart rightLeg = testModel.createPart("right_leg");
rightLeg.setPosition(20, 90);
Mesh2D rightLegMesh = Mesh2D.createQuad("right_leg_mesh", 20, 100);
rightLegMesh.setTexture(createSolidTexture(20, 100, 0xFF4169E1));
rightLeg.addMesh(rightLegMesh);
// 建立层级body 为根,其他作为 body 的子节点
//testModel.addPart(body);
body.addChild(head);
body.addChild(leftArm);
body.addChild(rightArm);
body.addChild(leftLeg);
body.addChild(rightLeg);
// 创建动画参数用于简单摆动
testModel.createParameter("arm_swing", -1.0f, 1.0f, 0f);
testModel.createParameter("leg_swing", -1.0f, 1.0f, 0f);
testModel.createParameter("head_rotation", -0.5f, 0.5f, 0f);
System.out.println("Humanoid model created with parts: " + testModel.getParts().size());
}
// 辅助:创建身体渐变/纯色纹理ByteBuffer RGBA
private Texture createSolidTexture(int w, int h, int rgba) {
ByteBuffer buf = MemoryUtil.memAlloc(w * h * 4);
byte a = (byte) ((rgba >> 24) & 0xFF);
byte r = (byte) ((rgba >> 16) & 0xFF);
byte g = (byte) ((rgba >> 8) & 0xFF);
byte b = (byte) (rgba & 0xFF);
for (int i = 0; i < w * h; i++) {
buf.put(r).put(g).put(b).put(a);
}
buf.flip();
Texture t = new Texture("solid_" + rgba + "_" + w + "x" + h, w, h, Texture.TextureFormat.RGBA, buf);
MemoryUtil.memFree(buf);
return t;
}
private Texture createHeadTexture() {
int width = 64, height = 64;
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float dx = (x - width / 2f) / (width / 2f);
float dy = (y - height / 2f) / (height / 2f);
float dist = (float) Math.sqrt(dx * dx + dy * dy);
int alpha = dist > 1.0f ? 0 : 255;
int r = (int) (240 * (1.0f - dist * 0.25f));
int g = (int) (200 * (1.0f - dist * 0.25f));
int b = (int) (180 * (1.0f - dist * 0.25f));
pixels[y * width + x] = (alpha << 24) | (r << 16) | (g << 8) | b;
}
}
return new Texture("head_tex", width, height, Texture.TextureFormat.RGBA, pixels);
}
private void loop() {
long last = System.nanoTime();
double nsPerUpdate = 1_000_000_000.0 / 60.0;
double accumulator = 0.0;
System.out.println("Entering main loop...");
while (running && !GLFW.glfwWindowShouldClose(window)) {
long now = System.nanoTime();
accumulator += (now - last) / nsPerUpdate;
last = now;
while (accumulator >= 1.0) {
update(1.0f / 60.0f);
accumulator -= 1.0;
}
render();
GLFW.glfwSwapBuffers(window);
GLFW.glfwPollEvents();
}
}
private void update(float dt) {
if (!animate) return;
animationTime += dt;
float armSwing = (float) Math.sin(animationTime * 3.0f) * 0.7f; // -0.7 .. 0.7
float legSwing = (float) Math.sin(animationTime * 3.0f + Math.PI) * 0.6f;
float headRot = (float) Math.sin(animationTime * 1.4f) * 0.15f;
testModel.setParameterValue("arm_swing", armSwing);
testModel.setParameterValue("leg_swing", legSwing);
testModel.setParameterValue("head_rotation", headRot);
// 将参数应用到部件(直接通过 API 设置即可)
ModelPart leftArm = testModel.getPart("left_arm");
ModelPart rightArm = testModel.getPart("right_arm");
ModelPart leftLeg = testModel.getPart("left_leg");
ModelPart rightLeg = testModel.getPart("right_leg");
ModelPart head = testModel.getPart("head");
if (leftArm != null) leftArm.setRotation(-0.8f * armSwing - 0.2f);
if (rightArm != null) rightArm.setRotation(0.8f * armSwing + 0.2f);
if (leftLeg != null) leftLeg.setRotation(0.6f * legSwing);
if (rightLeg != null) rightLeg.setRotation(-0.6f * legSwing);
if (head != null) head.setRotation(headRot);
testModel.update(dt);
}
private void render() {
ModelRender.setClearColor(0.18f, 0.18f, 0.25f, 1.0f);
ModelRender.render(1.0f / 60.0f, testModel);
// 每 5 秒输出一次统计
if ((int) (animationTime) % 5 == 0 && (animationTime - (int) animationTime) < 0.016) {
//System.out.println("Render stats: meshes=" + ModelRender.getRenderStats());
}
}
private void randomizeModel() {
System.out.println("Randomizing model...");
ModelPart body = testModel.getPart("body");
if (body != null) {
body.setPosition(200 + random.nextInt(400), 200 + random.nextInt(200));
}
for (ModelPart p : testModel.getParts()) {
p.setRotation((float) (random.nextFloat() * Math.PI * 2));
if (p.getName().equals("head")) {
p.setOpacity(0.6f + random.nextFloat() * 0.4f);
}
}
}
private void cleanup() {
System.out.println("Cleaning up resources...");
ModelRender.cleanup();
Texture.cleanupAll();
if (window != MemoryUtil.NULL) GLFW.glfwDestroyWindow(window);
GLFW.glfwTerminate();
GLFW.glfwSetErrorCallback(null).free();
System.out.println("Test completed");
}
}