refactor(model):重构模型数据包结构并增强光源系统

- 将 AnimationLayerData 类从 util 包移动到 data 包
- 将 BufferBuilder 类从 util 包移动到 buffer 包并更新包引用
- 为 LightSource 类添加辉光(Glow)支持及相关字段
- 扩展 LightSourceData 序列化类以包含辉光相关字段
- 新增 MeshData 类用于网格数据的序列化- 更新 Model2D 和 ModelData 的包引用以适应新的类结构
- 移除 ModelData 中重复的内部类定义,统一使用 data 包中的类- 为多个类添加作者信息注解
This commit is contained in:
tzdwindows 7
2025-10-12 08:16:42 +08:00
parent 22c3661d6e
commit fb1db942ed
11 changed files with 1087 additions and 851 deletions

View File

@@ -2,13 +2,12 @@ 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.buffer.BufferBuilder;
import com.chuangzhou.vivid2D.render.model.util.LightSource;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem; // 引入 PhysicsSystem
import com.chuangzhou.vivid2D.render.model.util.Texture;
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
import org.joml.Matrix3f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
@@ -149,78 +148,116 @@ public final class ModelRender {
uniform int uLightsIsAmbient[MAX_LIGHTS];
uniform int uLightCount;
// 辉光相关
uniform int uLightsIsGlow[MAX_LIGHTS];
uniform vec2 uLightsGlowDir[MAX_LIGHTS];
uniform float uLightsGlowIntensity[MAX_LIGHTS];
uniform float uLightsGlowRadius[MAX_LIGHTS];
uniform float uLightsGlowAmount[MAX_LIGHTS];
// 常用衰减系数(可在 shader 内微调)
const float ATT_CONST = 1.0;
const float ATT_LINEAR = 0.09;
const float ATT_QUAD = 0.032;
// 简单 Reinhard tone mapping避免过曝
vec3 toneMap(vec3 color) {
return color / (color + vec3(1.0));
}
void main() {
// 先采样纹理
vec4 tex = texture(uTexture, vTexCoord);
float alpha = tex.a * uOpacity;
if (alpha <= 0.001) discard;
// 如果没有光源,跳过光照计算(性能更好并且保持原始贴图色)
if (uLightCount == 0) {
vec3 base = tex.rgb * uColor.rgb;
// 简单的色调映射(防止数值过大)
base = clamp(base, 0.0, 1.0);
FragColor = vec4(base, alpha);
return;
}
// 基础颜色(纹理 * 部件颜色)
vec3 baseColor = tex.rgb * uColor.rgb;
// 全局环境光基线(可以适度提高以避免全黑
vec3 ambient = vec3(0.06); // 小环境光补偿
// 如果没有光源,仅返回基础颜色(节约性能
if (uLightCount == 0) {
vec3 outCol = clamp(baseColor, 0.0, 1.0);
if (uBlendMode == 1) outCol = tex.rgb + uColor.rgb;
else if (uBlendMode == 2) outCol = tex.rgb * uColor.rgb;
else if (uBlendMode == 3) outCol = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);
FragColor = vec4(outCol, alpha);
return;
}
// 环境光基线
vec3 ambientBase = vec3(0.06);
vec3 lighting = vec3(0.0);
vec3 glowAccum = vec3(0.0);
vec3 specularAccum = vec3(0.0);
// 累积环境光(来自被标记为环境光的光源
// 累积显式标记为环境光的光源
for (int i = 0; i < uLightCount; ++i) {
if (uLightsIsAmbient[i] == 1) {
lighting += uLightsColor[i] * uLightsIntensity[i];
}
}
// 加上基线环境光
lighting += ambient;
lighting += ambientBase;
// 对每个非环境光计算基于距离的衰减与简单高光
// 对每个非环境光计算物理式衰减 + 漫反射 + 简单高光 + 辉光(若启用)
for (int i = 0; i < uLightCount; ++i) {
if (uLightsIsAmbient[i] == 1) continue;
vec2 toLight = uLightsPos[i] - vWorldPos;
float dist = length(toLight);
// 标准物理式衰减
vec2 toLight2 = uLightsPos[i] - vWorldPos;
float dist = length(toLight2);
// 物理风格衰减
float attenuation = ATT_CONST / (ATT_CONST + ATT_LINEAR * dist + ATT_QUAD * dist * dist);
// 强度受光源强度和衰减影响
float radiance = uLightsIntensity[i] * attenuation;
// 漫反射在纯2D情景下法线与视线近似固定Z向
// 所以漫反射对所有片元是恒定的。我们用一个基于距离的柔和因子来模拟明暗变化。
float diffuseFactor = clamp(1.0 - (dist * 0.0015), 0.0, 1.0); // 通过调节常数控制半径感觉
// 漫反射(在二维中基于距离模拟衰减的明暗)
// 使用更平滑的距离曲线max(0, 1 - (dist / (radiusApprox)))
float radiusApprox = max(1.0, 1000.0 * attenuation); // 通过衰减估算影响半径
float diffuseFactor = clamp(1.0 - (dist / (radiusApprox + 0.0001)), 0.0, 1.0);
vec3 diff = uLightsColor[i] * radiance * diffuseFactor;
lighting += diff;
// 简单高光(基于视向与反射的大致模拟,产生亮点)
vec3 lightDir3 = normalize(vec3(toLight, 0.0));
// 简单高光(在 2D 中模拟亮点)
vec3 viewDir = vec3(0.0, 0.0, 1.0);
vec3 lightDir3 = normalize(vec3(toLight2, 0.0));
vec3 normal = vec3(0.0, 0.0, 1.0);
vec3 reflectDir = reflect(-lightDir3, normal);
float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 16.0); // 16 为高光粗糙度,可调
float specIntensity = 0.2; // 高光强度系数
float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
float specIntensity = 0.25;
specularAccum += uLightsColor[i] * radiance * specFactor * specIntensity;
// 若启用了辉光glow使用高斯风格衰减并支持方向性辉光
if (uLightsIsGlow[i] == 1) {
float glowRadius = max(0.0001, uLightsGlowRadius[i]);
float gdist = dist;
// 高斯分布exp(-(d^2) / (2 * sigma^2))
float sigma = glowRadius * 0.5; // sigma = radius * 0.5(经验值)
float gauss = exp(- (gdist * gdist) / (2.0 * sigma * sigma));
// 方向性因子:如果给出方向,使用方向与片元向量点积来增强朝向一侧的辉光
float dirFactor = 1.0;
vec2 glowDir = uLightsGlowDir[i];
if (length(glowDir) > 0.0001) {
vec2 ndir = normalize(glowDir);
vec2 toFrag = normalize(vWorldPos - uLightsPos[i]);
dirFactor = max(dot(ndir, toFrag), 0.0); // 只在方向半球贡献
}
float gIntensity = uLightsGlowIntensity[i];
float gAmount = uLightsGlowAmount[i];
vec3 glow = uLightsColor[i] * gauss * gIntensity * gAmount * dirFactor;
glowAccum += glow;
}
}
// 限制光照的最大值以避免过曝(可根据场景调整
vec3 totalLighting = min(lighting + specularAccum, vec3(2.0));
// 合并直接光照与高光后进行简单的色调映射(避免过曝
vec3 totalLighting = lighting + specularAccum;
// 防止数值过大,进行 Reinhard tone mapping
vec3 litMapped = toneMap(totalLighting);
vec3 finalColor = baseColor * litMapped;
// 将光照应用到基础颜色
vec3 finalColor = baseColor * totalLighting;
// 将辉光作为屏幕加色(加法混合),然后再做一次 tone map 以稳定输出
finalColor += glowAccum;
finalColor = toneMap(finalColor);
// 支持简单混合模式(保留有行为)
// 支持简单的 blend 模式(保留有行为)
if (uBlendMode == 1) finalColor = tex.rgb + uColor.rgb;
else if (uBlendMode == 2) finalColor = tex.rgb * uColor.rgb;
else if (uBlendMode == 3) finalColor = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);
@@ -276,12 +313,19 @@ public final class ModelRender {
com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i);
if (!l.isEnabled()) continue;
// 环境光的 position 在 shader 中不会用于距离计算,但我们也上传(安全)
// 基础属性
setUniformVec2Internal(sp, "uLightsPos[" + idx + "]", l.isAmbient() ? new org.joml.Vector2f(0f, 0f) : l.getPosition());
setUniformVec3Internal(sp, "uLightsColor[" + idx + "]", l.getColor());
setUniformFloatInternal(sp, "uLightsIntensity[" + idx + "]", l.getIntensity());
setUniformIntInternal(sp, "uLightsIsAmbient[" + idx + "]", l.isAmbient() ? 1 : 0);
// 辉光相关(如果没有被设置也安全地上传默认值)
setUniformIntInternal(sp, "uLightsIsGlow[" + idx + "]", l.isGlow() ? 1 : 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + idx + "]", l.getGlowDirection() != null ? l.getGlowDirection() : new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + idx + "]", l.getGlowIntensity());
setUniformFloatInternal(sp, "uLightsGlowRadius[" + idx + "]", l.getGlowRadius());
setUniformFloatInternal(sp, "uLightsGlowAmount[" + idx + "]", l.getGlowAmount());
idx++;
}
@@ -292,9 +336,15 @@ public final class ModelRender {
for (int i = idx; i < 8; i++) {
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
// color/pos 不严格必要,但清零更稳健
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f));
// 关闭辉光槽
setUniformIntInternal(sp, "uLightsIsGlow[" + i + "]", 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + i + "]", new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowRadius[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowAmount[" + i + "]", 0f);
}
}
@@ -462,8 +512,8 @@ public final class ModelRender {
if (!light.isEnabled()) continue;
// 绘制光源位置
com.chuangzhou.vivid2D.render.util.BufferBuilder bb =
new com.chuangzhou.vivid2D.render.util.BufferBuilder(1 * 4);
BufferBuilder bb =
new BufferBuilder(1 * 4);
bb.begin(GL11.GL_POINTS, 1);
bb.vertex(light.getPosition().x, light.getPosition().y, 0.5f, 0.5f);
bb.end();
@@ -569,7 +619,7 @@ public final class ModelRender {
*/
private static void drawCircleColliderWire(Vector2f center, float radius) {
int segments = Math.max(8, CIRCLE_SEGMENTS);
com.chuangzhou.vivid2D.render.util.BufferBuilder bb = new com.chuangzhou.vivid2D.render.util.BufferBuilder(segments * 4);
BufferBuilder bb = new BufferBuilder(segments * 4);
bb.begin(GL11.GL_LINE_LOOP, segments);
for (int i = 0; i < segments; i++) {
double ang = 2.0 * Math.PI * i / segments;
@@ -587,7 +637,7 @@ public final class ModelRender {
private static void drawRectangleColliderWire(Vector2f center, float width, float height) {
float halfW = width / 2.0f;
float halfH = height / 2.0f;
com.chuangzhou.vivid2D.render.util.BufferBuilder bb = new com.chuangzhou.vivid2D.render.util.BufferBuilder(4 * 4);
BufferBuilder bb = new BufferBuilder(4 * 4);
bb.begin(GL11.GL_LINE_LOOP, 4);
bb.vertex(center.x - halfW, center.y - halfH, 0.5f, 0.5f);
bb.vertex(center.x + halfW, center.y - halfH, 0.5f, 0.5f);

View File

@@ -1,5 +1,7 @@
package com.chuangzhou.vivid2D.render.model;
import com.chuangzhou.vivid2D.render.model.data.ModelData;
import com.chuangzhou.vivid2D.render.model.data.ModelMetadata;
import com.chuangzhou.vivid2D.render.model.util.*;
import org.joml.Matrix3f;

View File

@@ -1,6 +1,5 @@
package com.chuangzhou.vivid2D.render.util;
package com.chuangzhou.vivid2D.render.model.buffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
@@ -21,6 +20,7 @@ import java.nio.FloatBuffer;
* bb.end(); // 立即绘制并 cleanup
*
* 设计原则简单可靠方便把临时多顶点数据提交到 GPU
* @author tzdwindows 7
*/
public class BufferBuilder {
private static final int COMPONENTS_PER_VERTEX = 4; // x,y,u,v

View File

@@ -1,4 +1,7 @@
package com.chuangzhou.vivid2D.render.model.util;
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.AnimationClip;
import com.chuangzhou.vivid2D.render.model.util.AnimationLayer;
import java.io.Serializable;
import java.util.ArrayList;

View File

@@ -1,13 +1,14 @@
package com.chuangzhou.vivid2D.render.model.util;
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.LightSource;
import com.chuangzhou.vivid2D.render.model.util.SaveVector2f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import java.awt.*;
import java.io.Serializable;
/**
* LightSource 的序列化数据类
* LightSource 的序列化数据类扩展包含辉光/Glow 的序列化字段
* @author tzdwindows 7
*/
public class LightSourceData implements Serializable {
@@ -21,6 +22,13 @@ public class LightSourceData implements Serializable {
private boolean enabled;
private boolean isAmbient;
// ======= 辉光Glow相关序列化字段 =======
private boolean isGlow;
private String glowDirection; // 使用字符串格式存储 Vector2f
private float glowIntensity;
private float glowRadius;
private float glowAmount;
// 默认构造器
public LightSourceData() {
this.id = "light_" + System.currentTimeMillis();
@@ -29,6 +37,13 @@ public class LightSourceData implements Serializable {
this.intensity = 1.0f;
this.enabled = true;
this.isAmbient = false;
// 默认辉光值
this.isGlow = false;
this.glowDirection = "0,0";
this.glowIntensity = 0.0f;
this.glowRadius = 50.0f;
this.glowAmount = 1.0f;
}
// LightSource 对象构造
@@ -41,6 +56,13 @@ public class LightSourceData implements Serializable {
this.intensity = light.getIntensity();
this.enabled = light.isEnabled();
this.isAmbient = light.isAmbient();
// 辉光相关
this.isGlow = light.isGlow();
this.glowDirection = SaveVector2f.toString(light.getGlowDirection() != null ? light.getGlowDirection() : new Vector2f(0f, 0f));
this.glowIntensity = light.getGlowIntensity();
this.glowRadius = light.getGlowRadius();
this.glowAmount = light.getGlowAmount();
}
}
@@ -51,11 +73,33 @@ public class LightSourceData implements Serializable {
LightSource light;
if (isAmbient) {
// 使用环境光构造器
light = new LightSource(LightSource.vector3fToColor(col), intensity);
} else {
light = new LightSource(pos, LightSource.vector3fToColor(col), intensity);
// 使用包含辉光参数的构造器即便 isGlow false 也可以传入
light = new LightSource(
pos,
LightSource.vector3fToColor(col),
intensity,
isGlow,
SaveVector2f.fromString(glowDirection),
glowIntensity,
glowRadius,
glowAmount
);
}
light.setEnabled(enabled);
light.setAmbient(isAmbient);
// 如果使用了环境光构造器但需要设置辉光罕见通过 setter 设置
if (isAmbient) {
light.setGlow(isGlow);
light.setGlowDirection(SaveVector2f.fromString(glowDirection));
light.setGlowIntensity(glowIntensity);
light.setGlowRadius(glowRadius);
light.setGlowAmount(glowAmount);
}
return light;
}
@@ -68,6 +112,13 @@ public class LightSourceData implements Serializable {
copy.intensity = this.intensity;
copy.enabled = this.enabled;
copy.isAmbient = this.isAmbient;
copy.isGlow = this.isGlow;
copy.glowDirection = this.glowDirection;
copy.glowIntensity = this.glowIntensity;
copy.glowRadius = this.glowRadius;
copy.glowAmount = this.glowAmount;
return copy;
}
@@ -151,7 +202,49 @@ public class LightSourceData implements Serializable {
isAmbient = ambient;
}
// ==================== 工具方法 ====================
// ======= 辉光相关 Getter/Setter =======
public boolean isGlow() {
return isGlow;
}
public void setGlow(boolean glow) {
isGlow = glow;
}
public String getGlowDirection() {
return glowDirection;
}
public void setGlowDirection(String glowDirection) {
this.glowDirection = glowDirection;
}
public float getGlowIntensity() {
return glowIntensity;
}
public void setGlowIntensity(float glowIntensity) {
this.glowIntensity = glowIntensity;
}
public float getGlowRadius() {
return glowRadius;
}
public void setGlowRadius(float glowRadius) {
this.glowRadius = glowRadius;
}
public float getGlowAmount() {
return glowAmount;
}
public void setGlowAmount(float glowAmount) {
this.glowAmount = glowAmount;
}
// ==================== 工具方法向量形式 ====================
/**
* 设置位置为 Vector2f
@@ -181,6 +274,20 @@ public class LightSourceData implements Serializable {
return stringToVector3f(color);
}
/**
* 设置辉光方向Vector2f
*/
public void setGlowDirection(Vector2f dir) {
this.glowDirection = SaveVector2f.toString(dir);
}
/**
* 获取辉光方向为 Vector2f
*/
public Vector2f getGlowDirectionAsVector() {
return SaveVector2f.fromString(glowDirection);
}
@Override
public String toString() {
return "LightSourceData{" +
@@ -190,6 +297,11 @@ public class LightSourceData implements Serializable {
", intensity=" + intensity +
", enabled=" + enabled +
", isAmbient=" + isAmbient +
", isGlow=" + isGlow +
", glowDirection='" + glowDirection + '\'' +
", glowIntensity=" + glowIntensity +
", glowRadius=" + glowRadius +
", glowAmount=" + glowAmount +
'}';
}
}
}

View File

@@ -0,0 +1,55 @@
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import java.io.Serializable;
public 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;
}
}

View File

@@ -1,5 +1,8 @@
package com.chuangzhou.vivid2D.render.model;
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.util.*;
import org.joml.Vector2f;
import org.slf4j.Logger;
@@ -793,805 +796,7 @@ public class ModelData implements Serializable {
// ==================== 内部数据类 ====================
// ====== 修改后的 PartData包含液化数据的序列化/反序列化 ======
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 List<DeformerData> deformers;
// 保存液化笔划数据可保存多个笔划
public List<LiquifyStrokeData> liquifyStrokes;
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<>();
this.deformers = new ArrayList<>();
this.liquifyStrokes = new ArrayList<>();
}
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());
}
// 收集变形器序列化每个变形器为键值表
for (Deformer d : part.getDeformers()) {
try {
DeformerData dd = new DeformerData();
dd.type = d.getClass().getName();
dd.name = d.getName();
Map<String, String> map = new HashMap<>();
d.serialization(map); // 让变形器把自己的状态写入 map
dd.properties = map;
this.deformers.add(dd);
} catch (Exception e) {
// 忽略单个变形器序列化错误避免整个保存失败
e.printStackTrace();
}
}
// 尝试通过反射收集液化笔划数据兼容性如果 ModelPart 没有对应 API则跳过
try {
// 期望的方法签名 public List<YourStrokeClass> getLiquifyStrokes()
java.lang.reflect.Method m = part.getClass().getMethod("getLiquifyStrokes");
Object strokesObj = m.invoke(part);
if (strokesObj instanceof List<?>) {
List<?> strokes = (List<?>) strokesObj;
for (Object s : strokes) {
// 支持两种情况存储为自定义类型 getMode/getRadius/getStrength/getIterations/getPoints 方法
// 或者直接存储为通用 Map/POJO我们做宽松处理通过反射尽可能读取常见字段
LiquifyStrokeData strokeData = new LiquifyStrokeData();
try {
java.lang.reflect.Method gm = s.getClass().getMethod("getMode");
Object modeObj = gm.invoke(s);
if (modeObj != null) strokeData.mode = modeObj.toString();
} catch (NoSuchMethodException ignored) {}
try {
java.lang.reflect.Method gr = s.getClass().getMethod("getRadius");
Object r = gr.invoke(s);
if (r instanceof Number) strokeData.radius = ((Number) r).floatValue();
} catch (NoSuchMethodException ignored) {}
try {
java.lang.reflect.Method gs = s.getClass().getMethod("getStrength");
Object st = gs.invoke(s);
if (st instanceof Number) strokeData.strength = ((Number) st).floatValue();
} catch (NoSuchMethodException ignored) {}
try {
java.lang.reflect.Method gi = s.getClass().getMethod("getIterations");
Object it = gi.invoke(s);
if (it instanceof Number) strokeData.iterations = ((Number) it).intValue();
} catch (NoSuchMethodException ignored) {}
// 读取点列表
try {
java.lang.reflect.Method gp = s.getClass().getMethod("getPoints");
Object ptsObj = gp.invoke(s);
if (ptsObj instanceof List<?>) {
List<?> pts = (List<?>) ptsObj;
for (Object p : pts) {
// 支持 Vector2f 或自定义点类型 getX/getY/getPressure
LiquifyPointData pd = new LiquifyPointData();
if (p instanceof org.joml.Vector2f) {
org.joml.Vector2f v = (org.joml.Vector2f) p;
pd.x = v.x;
pd.y = v.y;
pd.pressure = 1.0f;
} else {
try {
java.lang.reflect.Method px = p.getClass().getMethod("getX");
java.lang.reflect.Method py = p.getClass().getMethod("getY");
Object ox = px.invoke(p);
Object oy = py.invoke(p);
if (ox instanceof Number && oy instanceof Number) {
pd.x = ((Number) ox).floatValue();
pd.y = ((Number) oy).floatValue();
}
try {
java.lang.reflect.Method pp = p.getClass().getMethod("getPressure");
Object op = pp.invoke(p);
if (op instanceof Number) pd.pressure = ((Number) op).floatValue();
} catch (NoSuchMethodException ignored2) {
pd.pressure = 1.0f;
}
} catch (NoSuchMethodException ex) {
// 最后尝试 Map 形式 x,y
if (p instanceof Map<?, ?>) {
Map<?, ?> mapP = (Map<?, ?>) p;
Object ox = mapP.get("x");
Object oy = mapP.get("y");
if (ox instanceof Number && oy instanceof Number) {
pd.x = ((Number) ox).floatValue();
pd.y = ((Number) oy).floatValue();
}
Object op = mapP.get("pressure");
if (op instanceof Number) pd.pressure = ((Number) op).floatValue();
}
}
}
strokeData.points.add(pd);
}
}
} catch (NoSuchMethodException ignored) {}
// 如果没有 mode则用默认 PUSH
if (strokeData.mode == null) strokeData.mode = ModelPart.LiquifyMode.PUSH.name();
this.liquifyStrokes.add(strokeData);
}
}
} catch (NoSuchMethodException ignored) {
// ModelPart 没有 getLiquifyStrokes 方法跳过向后兼容
} catch (Exception e) {
e.printStackTrace();
}
// 设置父级名称
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);
}
}
// 反序列化变形器仅创建已知类型其他类型可拓展
if (deformers != null) {
for (DeformerData dd : deformers) {
try {
String className = dd.type;
// 通过反射获取类并实例化必须有 public 构造函数(String name)
Class<?> clazz = Class.forName(className);
if (Deformer.class.isAssignableFrom(clazz)) {
Deformer deformer = (Deformer) clazz
.getConstructor(String.class)
.newInstance(dd.name);
// 反序列化属性
try {
deformer.deserialize(dd.properties != null ? dd.properties : new HashMap<>());
} catch (Exception ex) {
ex.printStackTrace();
}
part.addDeformer(deformer);
} else {
System.err.println("跳过无效的变形器类型: " + className);
}
} catch (Exception e) {
System.err.println("反序列化变形器失败: " + dd.type);
e.printStackTrace();
}
}
}
// 反序列化液化笔划如果 PartData 中存在 liquifyStrokes尝试在新创建的 part 上重放这些笔划
if (liquifyStrokes != null && !liquifyStrokes.isEmpty()) {
for (LiquifyStrokeData stroke : liquifyStrokes) {
// 尝试将 mode 转换为 ModelPart.LiquifyMode
ModelPart.LiquifyMode modeEnum = ModelPart.LiquifyMode.PUSH;
try {
modeEnum = ModelPart.LiquifyMode.valueOf(stroke.mode);
} catch (Exception ignored) {}
// 对每个点进行重放调用 applyLiquify存在于 ModelPart
if (stroke.points != null) {
for (LiquifyPointData p : stroke.points) {
try {
part.applyLiquify(new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations);
} catch (Exception e) {
// 如果 applyLiquify 不存在或签名不匹配则尝试通过反射调用名为 applyLiquify 的方法
try {
java.lang.reflect.Method am = part.getClass().getMethod("applyLiquify", Vector2f.class, float.class, float.class, ModelPart.LiquifyMode.class, int.class);
am.invoke(part, new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations);
} catch (NoSuchMethodException nsme) {
// 无法恢复液化 ModelPart 可能不支持液化存储/播放跳过
break;
} catch (Exception ex) {
ex.printStackTrace();
break;
}
}
}
}
}
}
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);
// 深拷贝 deformers 列表
copy.deformers = new ArrayList<>();
if (this.deformers != null) {
for (DeformerData d : this.deformers) {
DeformerData cd = new DeformerData();
cd.type = d.type;
cd.name = d.name;
cd.properties = (d.properties != null) ? new HashMap<>(d.properties) : new HashMap<>();
copy.deformers.add(cd);
}
}
// 深拷贝 liquifyStrokes
copy.liquifyStrokes = new ArrayList<>();
if (this.liquifyStrokes != null) {
for (LiquifyStrokeData s : this.liquifyStrokes) {
LiquifyStrokeData cs = new LiquifyStrokeData();
cs.mode = s.mode;
cs.radius = s.radius;
cs.strength = s.strength;
cs.iterations = s.iterations;
cs.points = new ArrayList<>();
if (s.points != null) {
for (LiquifyPointData p : s.points) {
LiquifyPointData cp = new LiquifyPointData();
cp.x = p.x;
cp.y = p.y;
cp.pressure = p.pressure;
cs.points.add(cp);
}
}
copy.liquifyStrokes.add(cs);
}
}
return copy;
}
/**
* 内部类序列化变形器数据结构
*/
public static class DeformerData implements Serializable {
private static final long serialVersionUID = 1L;
public String type; // 例如 "VertexDeformer"
public String name;
public Map<String, String> properties; // Deformer.serialization 填充
}
/**
* 内部类液化笔划数据Serializable
* 每个笔划有若干点以及笔划级别参数mode/radius/strength/iterations
*/
public static class LiquifyStrokeData implements Serializable {
private static final long serialVersionUID = 1L;
// LiquifyMode name()例如 "PUSH", "PULL"
public String mode = ModelPart.LiquifyMode.PUSH.name();
// 画笔半径与强度用于重放
public float radius = 50.0f;
public float strength = 0.5f;
public int iterations = 1;
// 笔划包含的点序列世界坐标
public List<LiquifyPointData> points = new ArrayList<>();
}
/**
* 内部类单个液化点数据
*/
public static class LiquifyPointData implements Serializable {
private static final long serialVersionUID = 1L;
public float x;
public float y;
public float pressure = 1.0f;
}
}
/**
* 网格数据
*/
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 Texture.TextureFilter minFilter;
public Texture.TextureFilter magFilter;
public Texture.TextureWrap wrapS;
public Texture.TextureWrap wrapT;
public boolean mipmapsEnabled;
public Map<String, String> metadata;
public TextureData() {
this.minFilter = Texture.TextureFilter.LINEAR;
this.magFilter = Texture.TextureFilter.LINEAR;
this.wrapS = Texture.TextureWrap.CLAMP_TO_EDGE;
this.wrapT = Texture.TextureWrap.CLAMP_TO_EDGE;
this.mipmapsEnabled = false;
this.metadata = new HashMap<>();
}
public TextureData(Texture texture) {
this();
this.name = texture.getName();
this.width = texture.getWidth();
this.height = texture.getHeight();
this.format = texture.getFormat();
this.minFilter = texture.getMinFilter();
this.magFilter = texture.getMagFilter();
this.wrapS = texture.getWrapS();
this.wrapT = texture.getWrapT();
this.mipmapsEnabled = texture.isMipmapsEnabled();
if (texture.hasPixelData()) {
this.imageData = texture.getPixelData();
//System.out.println("Using cached pixel data for texture: " + texture.getName());
} else {
//System.out.println("No cached data for texture: " + texture.getName() + ", extracting from GPU");
this.imageData = extractTextureData(texture);
}
}
private byte[] extractFromTextureInternal(Texture texture) {
if (texture.hasPixelData()) {
return texture.getPixelData();
}
throw new RuntimeException("No OpenGL context and no internal pixel data available");
}
/**
* 从纹理提取图像数据
*/
private byte[] extractTextureData(Texture texture) {
try {
// 确保有OpenGL上下文
if (!org.lwjgl.opengl.GL.getCapabilities().OpenGL45) {
System.err.println("OpenGL context not available for texture extraction");
// 尝试使用纹理的内部数据如果有
return extractFromTextureInternal(texture);
}
java.nio.ByteBuffer pixelData = texture.extractTextureData();
if (pixelData == null || pixelData.remaining() == 0) {
System.err.println("Texture data extraction returned null or empty buffer");
throw new RuntimeException("Failed to extract texture data");
}
// 验证数据大小
int expectedSize = width * height * format.getComponents();
if (pixelData.remaining() != expectedSize) {
System.err.println("Texture data size mismatch. Expected: " + expectedSize +
", Got: " + pixelData.remaining());
throw new RuntimeException("Texture data size mismatch");
}
byte[] data = new byte[pixelData.remaining()];
pixelData.get(data);
// 释放Native Memory
org.lwjgl.system.MemoryUtil.memFree(pixelData);
return data;
} catch (Exception e) {
logger.error("Critical error extracting texture data: {}", e.getMessage());
throw new RuntimeException("Failed to extract texture data for serialization", e);
}
}
/**
* 创建占位符纹理数据
*/
private byte[] createPlaceholderTextureData() {
int components = format.getComponents();
int dataSize = width * height * components;
byte[] data = new byte[dataSize];
// 创建简单的渐变纹理作为占位符
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = (y * width + x) * components;
if (components >= 1) data[index] = (byte)((x * 255) / width); // R
if (components >= 2) data[index + 1] = (byte)((y * 255) / height); // G
if (components >= 3) data[index + 2] = (byte)128; // B
if (components >= 4) data[index + 3] = (byte)255; // A
}
}
return data;
}
public Texture toTexture() {
try {
Texture texture = null;
if (imageData != null && imageData.length > 0) {
try {
java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocateDirect(imageData.length);
buffer.put(imageData);
buffer.flip();
texture = new Texture(name, width, height, format, buffer);
} catch (Exception e) {
logger.error("Failed to create texture from image data: {}", e.getMessage());
}
}
// 如果从图像数据创建失败尝试从文件创建
if (texture == null && filePath != null && !filePath.isEmpty()) {
try {
texture = loadTextureFromFile(filePath);
} catch (Exception e) {
logger.error("Failed to create texture from file: {}", e.getMessage());
}
}
// 如果以上方法都失败创建空纹理
if (texture == null) {
try {
texture = new Texture(name, width, height, format);
} catch (Exception e) {
logger.error("Failed to create empty texture: {}", e.getMessage());
throw e;
}
}
// 应用纹理参数
if (texture != null) {
try {
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
texture.setWrapS(wrapS);
texture.setWrapT(wrapT);
if (mipmapsEnabled) {
texture.generateMipmaps();
}
} catch (Exception e) {
logger.error("Failed to apply texture parameters: {}", e.getMessage());
}
}
return texture;
} catch (Exception e) {
logger.error("Critical error in toTexture() for '{}': {}", name, e.getMessage());
e.printStackTrace();
return createSimpleFallbackTexture();
}
}
/**
* 创建简单的后备纹理
*/
private Texture createSimpleFallbackTexture() {
try {
// 创建一个非常简单的纯色纹理
return Texture.createSolidColor(name + "_simple_fallback", 64, 64, 0xFFFFFF00); // 黄色
} catch (Exception e) {
logger.error("Even fallback texture creation failed: {}", e.getMessage());
return null;
}
}
/**
* 从文件加载纹理
*/
private Texture loadTextureFromFile(String filePath) {
try {
// 使用Texture类的静态方法从文件加载
Texture texture = Texture.createFromFile(name, filePath);
// 应用保存的纹理参数
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
texture.setWrapS(wrapS);
texture.setWrapT(wrapT);
if (mipmapsEnabled) {
texture.generateMipmaps();
}
return texture;
} catch (Exception e) {
logger.error("Failed to load texture from file: {} - {}", filePath, e.getMessage());
return createFallbackTexture();
}
}
/**
* 获取纹理数据的估计内存使用量字节
*/
public long getEstimatedMemoryUsage() {
long baseMemory = (long) width * height * format.getComponents();
// 如果启用了mipmaps加上mipmaps的内存
if (mipmapsEnabled) {
return baseMemory * 4L / 3L; // mipmaps大约增加1/3内存
}
return baseMemory;
}
/**
* 将纹理数据保存到图像文件
*/
public boolean saveToFile(String filePath, String format) {
if (imageData == null) {
logger.error("No image data to save");
return false;
}
try {
// 创建临时纹理并保存
Texture tempTexture = this.toTexture();
boolean success = tempTexture.saveToFile(filePath, format);
tempTexture.dispose(); // 清理临时纹理
return success;
} catch (Exception e) {
logger.error("Failed to save texture data to file: {}", e.getMessage());
return false;
}
}
public boolean saveToFile(String filePath) {
return saveToFile(filePath, "png"); // 默认保存为PNG
}
/**
* 从文件路径创建纹理数据
*/
public static TextureData fromFile(String name, String filePath) {
TextureData data = new TextureData();
data.name = name;
data.filePath = filePath;
// 预加载图像信息
try {
Texture.ImageInfo info = Texture.getImageInfo(filePath);
data.width = info.width;
data.height = info.height;
data.format = Texture.getTextureFormat(info.components);
} catch (Exception e) {
System.err.println("Failed to get image info: " + e.getMessage());
// 设置默认值
data.width = 64;
data.height = 64;
data.format = Texture.TextureFormat.RGBA;
}
return data;
}
/**
* 从内存数据创建纹理数据
*/
public static TextureData fromMemory(String name, byte[] imageData, int width, int height, Texture.TextureFormat format) {
TextureData data = new TextureData();
data.name = name;
data.setImageData(imageData, width, height, format);
return data;
}
/**
* 验证纹理数据的完整性
*/
public boolean validate() {
if (name == null || name.trim().isEmpty()) {
return false;
}
if (width <= 0 || height <= 0) {
return false;
}
if (format == null) {
return false;
}
// 检查图像数据大小是否匹配
if (imageData != null) {
int expectedSize = width * height * format.getComponents();
if (imageData.length != expectedSize) {
System.err.println("Texture data size mismatch. Expected: " + expectedSize + ", Got: " + imageData.length);
return false;
}
}
return true;
}
/**
* 创建后备纹理当主要方法失败时使用
*/
private Texture createFallbackTexture() {
try {
// 创建一个棋盘格纹理作为后备
return Texture.createCheckerboard(
name + "_fallback",
Math.max(32, width),
Math.max(32, height),
8,
0xFFFF0000, // 红色
0xFF0000FF // 蓝色
);
} catch (Exception e) {
// 如果连后备纹理都创建失败抛出异常
logger.error("Failed to create fallback texture: {}", e.getMessage());
throw new RuntimeException("Failed to create fallback texture", e);
}
}
/**
* 设置文件路径用于从文件加载纹理
*/
public void setFilePath(String filePath) {
this.filePath = filePath;
// 清除imageData因为我们将从文件加载
this.imageData = null;
}
/**
* 设置图像数据用于从内存数据创建纹理
*/
public void setImageData(byte[] imageData, int width, int height, Texture.TextureFormat format) {
this.imageData = imageData;
this.width = width;
this.height = height;
this.format = format;
// 清除filePath因为我们将使用内存数据
this.filePath = null;
}
/**
* 添加元数据
*/
public void addMetadata(String key, String value) {
this.metadata.put(key, value);
}
/**
* 获取元数据
*/
public String getMetadata(String key) {
return this.metadata.get(key);
}
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;
copy.minFilter = this.minFilter;
copy.magFilter = this.magFilter;
copy.wrapS = this.wrapS;
copy.wrapT = this.wrapT;
copy.mipmapsEnabled = this.mipmapsEnabled;
copy.metadata = new HashMap<>(this.metadata);
return copy;
}
@Override
public String toString() {
return "TextureData{" +
"name='" + name + '\'' +
", size=" + width + "x" + height +
", format=" + format +
", hasImageData=" + (imageData != null) +
", filePath=" + (filePath != null ? "'" + filePath + "'" : "null") +
'}';
}
}
// ---------- 物理数据的序列化类 ----------
public static class ParticleData implements Serializable {

View File

@@ -1,4 +1,4 @@
package com.chuangzhou.vivid2D.render.model.util;
package com.chuangzhou.vivid2D.render.model.data;
import org.joml.Vector2f;

View File

@@ -0,0 +1,355 @@
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Vector2f;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author tzdwindows 7
*/
public 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 List<DeformerData> deformers;
// 保存液化笔划数据(可保存多个笔划)
public List<LiquifyStrokeData> liquifyStrokes;
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<>();
this.deformers = new ArrayList<>();
this.liquifyStrokes = new ArrayList<>();
}
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());
}
// 收集变形器(序列化每个变形器为键值表)
for (Deformer d : part.getDeformers()) {
try {
DeformerData dd = new DeformerData();
dd.type = d.getClass().getName();
dd.name = d.getName();
Map<String, String> map = new HashMap<>();
d.serialization(map); // 让变形器把自己的状态写入 map
dd.properties = map;
this.deformers.add(dd);
} catch (Exception e) {
// 忽略单个变形器序列化错误,避免整个保存失败
e.printStackTrace();
}
}
// 尝试通过反射收集液化笔划数据(兼容性:如果 ModelPart 没有对应 API则跳过
try {
// 期望的方法签名: public List<YourStrokeClass> getLiquifyStrokes()
java.lang.reflect.Method m = part.getClass().getMethod("getLiquifyStrokes");
Object strokesObj = m.invoke(part);
if (strokesObj instanceof List<?>) {
List<?> strokes = (List<?>) strokesObj;
for (Object s : strokes) {
// 支持两种情况:存储为自定义类型(有 getMode/getRadius/getStrength/getIterations/getPoints 方法)
// 或者直接存储为通用 Map/POJO。我们做宽松处理通过反射尽可能读取常见字段。
LiquifyStrokeData strokeData = new LiquifyStrokeData();
try {
java.lang.reflect.Method gm = s.getClass().getMethod("getMode");
Object modeObj = gm.invoke(s);
if (modeObj != null) strokeData.mode = modeObj.toString();
} catch (NoSuchMethodException ignored) {}
try {
java.lang.reflect.Method gr = s.getClass().getMethod("getRadius");
Object r = gr.invoke(s);
if (r instanceof Number) strokeData.radius = ((Number) r).floatValue();
} catch (NoSuchMethodException ignored) {}
try {
java.lang.reflect.Method gs = s.getClass().getMethod("getStrength");
Object st = gs.invoke(s);
if (st instanceof Number) strokeData.strength = ((Number) st).floatValue();
} catch (NoSuchMethodException ignored) {}
try {
java.lang.reflect.Method gi = s.getClass().getMethod("getIterations");
Object it = gi.invoke(s);
if (it instanceof Number) strokeData.iterations = ((Number) it).intValue();
} catch (NoSuchMethodException ignored) {}
// 读取点列表
try {
java.lang.reflect.Method gp = s.getClass().getMethod("getPoints");
Object ptsObj = gp.invoke(s);
if (ptsObj instanceof List<?>) {
List<?> pts = (List<?>) ptsObj;
for (Object p : pts) {
// 支持 Vector2f 或自定义点类型(有 getX/getY/getPressure
LiquifyPointData pd = new LiquifyPointData();
if (p instanceof org.joml.Vector2f) {
org.joml.Vector2f v = (org.joml.Vector2f) p;
pd.x = v.x;
pd.y = v.y;
pd.pressure = 1.0f;
} else {
try {
java.lang.reflect.Method px = p.getClass().getMethod("getX");
java.lang.reflect.Method py = p.getClass().getMethod("getY");
Object ox = px.invoke(p);
Object oy = py.invoke(p);
if (ox instanceof Number && oy instanceof Number) {
pd.x = ((Number) ox).floatValue();
pd.y = ((Number) oy).floatValue();
}
try {
java.lang.reflect.Method pp = p.getClass().getMethod("getPressure");
Object op = pp.invoke(p);
if (op instanceof Number) pd.pressure = ((Number) op).floatValue();
} catch (NoSuchMethodException ignored2) {
pd.pressure = 1.0f;
}
} catch (NoSuchMethodException ex) {
// 最后尝试 Map 形式(键 x,y
if (p instanceof Map<?, ?>) {
Map<?, ?> mapP = (Map<?, ?>) p;
Object ox = mapP.get("x");
Object oy = mapP.get("y");
if (ox instanceof Number && oy instanceof Number) {
pd.x = ((Number) ox).floatValue();
pd.y = ((Number) oy).floatValue();
}
Object op = mapP.get("pressure");
if (op instanceof Number) pd.pressure = ((Number) op).floatValue();
}
}
}
strokeData.points.add(pd);
}
}
} catch (NoSuchMethodException ignored) {}
// 如果没有 mode则用默认 PUSH
if (strokeData.mode == null) strokeData.mode = ModelPart.LiquifyMode.PUSH.name();
this.liquifyStrokes.add(strokeData);
}
}
} catch (NoSuchMethodException ignored) {
// ModelPart 没有 getLiquifyStrokes 方法,跳过(向后兼容)
} catch (Exception e) {
e.printStackTrace();
}
// 设置父级名称
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);
}
}
// 反序列化变形器(仅创建已知类型,其他类型可拓展)
if (deformers != null) {
for (DeformerData dd : deformers) {
try {
String className = dd.type;
// 通过反射获取类并实例化(必须有 public 构造函数(String name)
Class<?> clazz = Class.forName(className);
if (Deformer.class.isAssignableFrom(clazz)) {
Deformer deformer = (Deformer) clazz
.getConstructor(String.class)
.newInstance(dd.name);
// 反序列化属性
try {
deformer.deserialize(dd.properties != null ? dd.properties : new HashMap<>());
} catch (Exception ex) {
ex.printStackTrace();
}
part.addDeformer(deformer);
} else {
System.err.println("跳过无效的变形器类型: " + className);
}
} catch (Exception e) {
System.err.println("反序列化变形器失败: " + dd.type);
e.printStackTrace();
}
}
}
// 反序列化液化笔划:如果 PartData 中存在 liquifyStrokes尝试在新创建的 part 上重放这些笔划
if (liquifyStrokes != null && !liquifyStrokes.isEmpty()) {
for (LiquifyStrokeData stroke : liquifyStrokes) {
// 尝试将 mode 转换为 ModelPart.LiquifyMode
ModelPart.LiquifyMode modeEnum = ModelPart.LiquifyMode.PUSH;
try {
modeEnum = ModelPart.LiquifyMode.valueOf(stroke.mode);
} catch (Exception ignored) {}
// 对每个点进行重放:调用 applyLiquify存在于 ModelPart
if (stroke.points != null) {
for (LiquifyPointData p : stroke.points) {
try {
part.applyLiquify(new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations);
} catch (Exception e) {
// 如果 applyLiquify 不存在或签名不匹配,则尝试通过反射调用名为 applyLiquify 的方法
try {
java.lang.reflect.Method am = part.getClass().getMethod("applyLiquify", Vector2f.class, float.class, float.class, ModelPart.LiquifyMode.class, int.class);
am.invoke(part, new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations);
} catch (NoSuchMethodException nsme) {
// 无法恢复液化(该 ModelPart 可能不支持液化存储/播放),跳过
break;
} catch (Exception ex) {
ex.printStackTrace();
break;
}
}
}
}
}
}
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);
// 深拷贝 deformers 列表
copy.deformers = new ArrayList<>();
if (this.deformers != null) {
for (DeformerData d : this.deformers) {
DeformerData cd = new DeformerData();
cd.type = d.type;
cd.name = d.name;
cd.properties = (d.properties != null) ? new HashMap<>(d.properties) : new HashMap<>();
copy.deformers.add(cd);
}
}
// 深拷贝 liquifyStrokes
copy.liquifyStrokes = new ArrayList<>();
if (this.liquifyStrokes != null) {
for (LiquifyStrokeData s : this.liquifyStrokes) {
LiquifyStrokeData cs = new LiquifyStrokeData();
cs.mode = s.mode;
cs.radius = s.radius;
cs.strength = s.strength;
cs.iterations = s.iterations;
cs.points = new ArrayList<>();
if (s.points != null) {
for (LiquifyPointData p : s.points) {
LiquifyPointData cp = new LiquifyPointData();
cp.x = p.x;
cp.y = p.y;
cp.pressure = p.pressure;
cs.points.add(cp);
}
}
copy.liquifyStrokes.add(cs);
}
}
return copy;
}
/**
* 内部类:序列化变形器数据结构
*/
public static class DeformerData implements Serializable {
private static final long serialVersionUID = 1L;
public String type; // 例如 "VertexDeformer"
public String name;
public Map<String, String> properties; // 由 Deformer.serialization 填充
}
/**
* 内部类液化笔划数据Serializable
* 每个笔划有若干点以及笔划级别参数mode/radius/strength/iterations
*/
public static class LiquifyStrokeData implements Serializable {
private static final long serialVersionUID = 1L;
// LiquifyMode 的 name(),例如 "PUSH", "PULL" 等
public String mode = ModelPart.LiquifyMode.PUSH.name();
// 画笔半径与强度(用于重放)
public float radius = 50.0f;
public float strength = 0.5f;
public int iterations = 1;
// 笔划包含的点序列(世界坐标)
public List<LiquifyPointData> points = new ArrayList<>();
}
/**
* 内部类:单个液化点数据
*/
public static class LiquifyPointData implements Serializable {
private static final long serialVersionUID = 1L;
public float x;
public float y;
public float pressure = 1.0f;
}
}

View File

@@ -0,0 +1,412 @@
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.Texture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class TextureData implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(TextureData.class);
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 Texture.TextureFilter minFilter;
public Texture.TextureFilter magFilter;
public Texture.TextureWrap wrapS;
public Texture.TextureWrap wrapT;
public boolean mipmapsEnabled;
public Map<String, String> metadata;
public TextureData() {
this.minFilter = Texture.TextureFilter.LINEAR;
this.magFilter = Texture.TextureFilter.LINEAR;
this.wrapS = Texture.TextureWrap.CLAMP_TO_EDGE;
this.wrapT = Texture.TextureWrap.CLAMP_TO_EDGE;
this.mipmapsEnabled = false;
this.metadata = new HashMap<>();
}
public TextureData(Texture texture) {
this();
this.name = texture.getName();
this.width = texture.getWidth();
this.height = texture.getHeight();
this.format = texture.getFormat();
this.minFilter = texture.getMinFilter();
this.magFilter = texture.getMagFilter();
this.wrapS = texture.getWrapS();
this.wrapT = texture.getWrapT();
this.mipmapsEnabled = texture.isMipmapsEnabled();
if (texture.hasPixelData()) {
this.imageData = texture.getPixelData();
//System.out.println("Using cached pixel data for texture: " + texture.getName());
} else {
//System.out.println("No cached data for texture: " + texture.getName() + ", extracting from GPU");
this.imageData = extractTextureData(texture);
}
}
private byte[] extractFromTextureInternal(Texture texture) {
if (texture.hasPixelData()) {
return texture.getPixelData();
}
throw new RuntimeException("No OpenGL context and no internal pixel data available");
}
/**
* 从纹理提取图像数据
*/
private byte[] extractTextureData(Texture texture) {
try {
// 确保有OpenGL上下文
if (!org.lwjgl.opengl.GL.getCapabilities().OpenGL45) {
System.err.println("OpenGL context not available for texture extraction");
// 尝试使用纹理的内部数据(如果有)
return extractFromTextureInternal(texture);
}
java.nio.ByteBuffer pixelData = texture.extractTextureData();
if (pixelData == null || pixelData.remaining() == 0) {
System.err.println("Texture data extraction returned null or empty buffer");
throw new RuntimeException("Failed to extract texture data");
}
// 验证数据大小
int expectedSize = width * height * format.getComponents();
if (pixelData.remaining() != expectedSize) {
System.err.println("Texture data size mismatch. Expected: " + expectedSize +
", Got: " + pixelData.remaining());
throw new RuntimeException("Texture data size mismatch");
}
byte[] data = new byte[pixelData.remaining()];
pixelData.get(data);
// 释放Native Memory
org.lwjgl.system.MemoryUtil.memFree(pixelData);
return data;
} catch (Exception e) {
logger.error("Critical error extracting texture data: {}", e.getMessage());
throw new RuntimeException("Failed to extract texture data for serialization", e);
}
}
/**
* 创建占位符纹理数据
*/
private byte[] createPlaceholderTextureData() {
int components = format.getComponents();
int dataSize = width * height * components;
byte[] data = new byte[dataSize];
// 创建简单的渐变纹理作为占位符
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = (y * width + x) * components;
if (components >= 1) data[index] = (byte)((x * 255) / width); // R
if (components >= 2) data[index + 1] = (byte)((y * 255) / height); // G
if (components >= 3) data[index + 2] = (byte)128; // B
if (components >= 4) data[index + 3] = (byte)255; // A
}
}
return data;
}
public Texture toTexture() {
try {
Texture texture = null;
if (imageData != null && imageData.length > 0) {
try {
java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocateDirect(imageData.length);
buffer.put(imageData);
buffer.flip();
texture = new Texture(name, width, height, format, buffer);
} catch (Exception e) {
logger.error("Failed to create texture from image data: {}", e.getMessage());
}
}
// 如果从图像数据创建失败,尝试从文件创建
if (texture == null && filePath != null && !filePath.isEmpty()) {
try {
texture = loadTextureFromFile(filePath);
} catch (Exception e) {
logger.error("Failed to create texture from file: {}", e.getMessage());
}
}
// 如果以上方法都失败,创建空纹理
if (texture == null) {
try {
texture = new Texture(name, width, height, format);
} catch (Exception e) {
logger.error("Failed to create empty texture: {}", e.getMessage());
throw e;
}
}
// 应用纹理参数
if (texture != null) {
try {
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
texture.setWrapS(wrapS);
texture.setWrapT(wrapT);
if (mipmapsEnabled) {
texture.generateMipmaps();
}
} catch (Exception e) {
logger.error("Failed to apply texture parameters: {}", e.getMessage());
}
}
return texture;
} catch (Exception e) {
logger.error("Critical error in toTexture() for '{}': {}", name, e.getMessage());
e.printStackTrace();
return createSimpleFallbackTexture();
}
}
/**
* 创建简单的后备纹理
*/
private Texture createSimpleFallbackTexture() {
try {
// 创建一个非常简单的纯色纹理
return Texture.createSolidColor(name + "_simple_fallback", 64, 64, 0xFFFFFF00); // 黄色
} catch (Exception e) {
logger.error("Even fallback texture creation failed: {}", e.getMessage());
return null;
}
}
/**
* 从文件加载纹理
*/
private Texture loadTextureFromFile(String filePath) {
try {
// 使用Texture类的静态方法从文件加载
Texture texture = Texture.createFromFile(name, filePath);
// 应用保存的纹理参数
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
texture.setWrapS(wrapS);
texture.setWrapT(wrapT);
if (mipmapsEnabled) {
texture.generateMipmaps();
}
return texture;
} catch (Exception e) {
logger.error("Failed to load texture from file: {} - {}", filePath, e.getMessage());
return createFallbackTexture();
}
}
/**
* 获取纹理数据的估计内存使用量(字节)
*/
public long getEstimatedMemoryUsage() {
long baseMemory = (long) width * height * format.getComponents();
// 如果启用了mipmaps加上mipmaps的内存
if (mipmapsEnabled) {
return baseMemory * 4L / 3L; // mipmaps大约增加1/3内存
}
return baseMemory;
}
/**
* 将纹理数据保存到图像文件
*/
public boolean saveToFile(String filePath, String format) {
if (imageData == null) {
logger.error("No image data to save");
return false;
}
try {
// 创建临时纹理并保存
Texture tempTexture = this.toTexture();
boolean success = tempTexture.saveToFile(filePath, format);
tempTexture.dispose(); // 清理临时纹理
return success;
} catch (Exception e) {
logger.error("Failed to save texture data to file: {}", e.getMessage());
return false;
}
}
public boolean saveToFile(String filePath) {
return saveToFile(filePath, "png"); // 默认保存为PNG
}
/**
* 从文件路径创建纹理数据
*/
public static TextureData fromFile(String name, String filePath) {
TextureData data = new TextureData();
data.name = name;
data.filePath = filePath;
// 预加载图像信息
try {
Texture.ImageInfo info = Texture.getImageInfo(filePath);
data.width = info.width;
data.height = info.height;
data.format = Texture.getTextureFormat(info.components);
} catch (Exception e) {
System.err.println("Failed to get image info: " + e.getMessage());
// 设置默认值
data.width = 64;
data.height = 64;
data.format = Texture.TextureFormat.RGBA;
}
return data;
}
/**
* 从内存数据创建纹理数据
*/
public static TextureData fromMemory(String name, byte[] imageData, int width, int height, Texture.TextureFormat format) {
TextureData data = new TextureData();
data.name = name;
data.setImageData(imageData, width, height, format);
return data;
}
/**
* 验证纹理数据的完整性
*/
public boolean validate() {
if (name == null || name.trim().isEmpty()) {
return false;
}
if (width <= 0 || height <= 0) {
return false;
}
if (format == null) {
return false;
}
// 检查图像数据大小是否匹配
if (imageData != null) {
int expectedSize = width * height * format.getComponents();
if (imageData.length != expectedSize) {
System.err.println("Texture data size mismatch. Expected: " + expectedSize + ", Got: " + imageData.length);
return false;
}
}
return true;
}
/**
* 创建后备纹理(当主要方法失败时使用)
*/
private Texture createFallbackTexture() {
try {
// 创建一个棋盘格纹理作为后备
return Texture.createCheckerboard(
name + "_fallback",
Math.max(32, width),
Math.max(32, height),
8,
0xFFFF0000, // 红色
0xFF0000FF // 蓝色
);
} catch (Exception e) {
// 如果连后备纹理都创建失败,抛出异常
logger.error("Failed to create fallback texture: {}", e.getMessage());
throw new RuntimeException("Failed to create fallback texture", e);
}
}
/**
* 设置文件路径(用于从文件加载纹理)
*/
public void setFilePath(String filePath) {
this.filePath = filePath;
// 清除imageData因为我们将从文件加载
this.imageData = null;
}
/**
* 设置图像数据(用于从内存数据创建纹理)
*/
public void setImageData(byte[] imageData, int width, int height, Texture.TextureFormat format) {
this.imageData = imageData;
this.width = width;
this.height = height;
this.format = format;
// 清除filePath因为我们将使用内存数据
this.filePath = null;
}
/**
* 添加元数据
*/
public void addMetadata(String key, String value) {
this.metadata.put(key, value);
}
/**
* 获取元数据
*/
public String getMetadata(String key) {
return this.metadata.get(key);
}
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;
copy.minFilter = this.minFilter;
copy.magFilter = this.magFilter;
copy.wrapS = this.wrapS;
copy.wrapT = this.wrapT;
copy.mipmapsEnabled = this.mipmapsEnabled;
copy.metadata = new HashMap<>(this.metadata);
return copy;
}
@Override
public String toString() {
return "TextureData{" +
"name='" + name + '\'' +
", size=" + width + "x" + height +
", format=" + format +
", hasImageData=" + (imageData != null) +
", filePath=" + (filePath != null ? "'" + filePath + "'" : "null") +
'}';
}
}

View File

@@ -5,6 +5,10 @@ import org.joml.Vector3f;
import java.awt.*;
/**
* 光源系统
* @author tzdwindows 7
*/
public class LightSource {
private Vector2f position;
private Vector3f color;
@@ -12,6 +16,13 @@ public class LightSource {
private boolean enabled = true;
private boolean isAmbient = false; // 是否为环境光
// ---- 辉光glow / bloom-like支持 ----
private boolean isGlow = false; // 是否产生辉光
private Vector2f glowDirection = new Vector2f(0f, 0f); // 方向性辉光方向(可为 0 向量表示无方向)
private float glowIntensity = 0f; // 辉光的强度系数(影响亮度)
private float glowRadius = 50f; // 辉光影响半径(像素/单位)
private float glowAmount = 1.0f; // 辉光权重 / 整体强度放大器
public LightSource(Vector2f pos, Color color, float intensity) {
this.position = pos;
this.color = colorToVector3f(color);
@@ -26,6 +37,19 @@ public class LightSource {
this.isAmbient = true;
}
// 带辉光参数
public LightSource(Vector2f pos, Color color, float intensity,
boolean isGlow, Vector2f glowDirection, float glowIntensity, float glowRadius, float glowAmount) {
this.position = pos;
this.color = colorToVector3f(color);
this.intensity = intensity;
this.isGlow = isGlow;
this.glowDirection = glowDirection != null ? glowDirection : new Vector2f(0f, 0f);
this.glowIntensity = glowIntensity;
this.glowRadius = glowRadius;
this.glowAmount = glowAmount;
}
public static Vector3f colorToVector3f(Color color) {
if (color == null) return new Vector3f(1, 1, 1);
return new Vector3f(
@@ -58,4 +82,22 @@ public class LightSource {
// 判断是否为环境光
public boolean isAmbient() { return isAmbient; }
public void setAmbient(boolean ambient) { this.isAmbient = ambient; }
}
// ---- 辉光相关的 getter / setter ----
public boolean isGlow() { return isGlow; }
public void setGlow(boolean glow) { this.isGlow = glow; }
public Vector2f getGlowDirection() { return glowDirection; }
public void setGlowDirection(Vector2f glowDirection) {
this.glowDirection = glowDirection != null ? glowDirection : new Vector2f(0f, 0f);
}
public float getGlowIntensity() { return glowIntensity; }
public void setGlowIntensity(float glowIntensity) { this.glowIntensity = glowIntensity; }
public float getGlowRadius() { return glowRadius; }
public void setGlowRadius(float glowRadius) { this.glowRadius = glowRadius; }
public float getGlowAmount() { return glowAmount; }
public void setGlowAmount(float glowAmount) { this.glowAmount = glowAmount; }
}