refactor(model):重构模型数据包结构并增强光源系统
- 将 AnimationLayerData 类从 util 包移动到 data 包 - 将 BufferBuilder 类从 util 包移动到 buffer 包并更新包引用 - 为 LightSource 类添加辉光(Glow)支持及相关字段 - 扩展 LightSourceData 序列化类以包含辉光相关字段 - 新增 MeshData 类用于网格数据的序列化- 更新 Model2D 和 ModelData 的包引用以适应新的类结构 - 移除 ModelData 中重复的内部类定义,统一使用 data 包中的类- 为多个类添加作者信息注解
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.chuangzhou.vivid2D.render.model.util;
|
||||
package com.chuangzhou.vivid2D.render.model.data;
|
||||
|
||||
import org.joml.Vector2f;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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") +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user