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.Model2D;
|
||||||
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
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.LightSource;
|
||||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||||
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem; // 引入 PhysicsSystem
|
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
|
||||||
import com.chuangzhou.vivid2D.render.model.util.Texture;
|
|
||||||
import org.joml.Matrix3f;
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Vector2f;
|
import org.joml.Vector2f;
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.joml.Vector4f;
|
import org.joml.Vector4f;
|
||||||
import org.lwjgl.opengl.*;
|
import org.lwjgl.opengl.*;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
@@ -149,78 +148,116 @@ public final class ModelRender {
|
|||||||
uniform int uLightsIsAmbient[MAX_LIGHTS];
|
uniform int uLightsIsAmbient[MAX_LIGHTS];
|
||||||
uniform int uLightCount;
|
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 内微调)
|
// 常用衰减系数(可在 shader 内微调)
|
||||||
const float ATT_CONST = 1.0;
|
const float ATT_CONST = 1.0;
|
||||||
const float ATT_LINEAR = 0.09;
|
const float ATT_LINEAR = 0.09;
|
||||||
const float ATT_QUAD = 0.032;
|
const float ATT_QUAD = 0.032;
|
||||||
|
|
||||||
|
// 简单 Reinhard tone mapping,避免过曝
|
||||||
|
vec3 toneMap(vec3 color) {
|
||||||
|
return color / (color + vec3(1.0));
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// 先采样纹理
|
// 先采样纹理
|
||||||
vec4 tex = texture(uTexture, vTexCoord);
|
vec4 tex = texture(uTexture, vTexCoord);
|
||||||
float alpha = tex.a * uOpacity;
|
float alpha = tex.a * uOpacity;
|
||||||
if (alpha <= 0.001) discard;
|
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 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 lighting = vec3(0.0);
|
||||||
|
vec3 glowAccum = vec3(0.0);
|
||||||
vec3 specularAccum = vec3(0.0);
|
vec3 specularAccum = vec3(0.0);
|
||||||
|
|
||||||
// 累积环境光(来自被标记为环境光的光源)
|
// 累积显式标记为环境光的光源
|
||||||
for (int i = 0; i < uLightCount; ++i) {
|
for (int i = 0; i < uLightCount; ++i) {
|
||||||
if (uLightsIsAmbient[i] == 1) {
|
if (uLightsIsAmbient[i] == 1) {
|
||||||
lighting += uLightsColor[i] * uLightsIntensity[i];
|
lighting += uLightsColor[i] * uLightsIntensity[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 加上基线环境光
|
lighting += ambientBase;
|
||||||
lighting += ambient;
|
|
||||||
|
|
||||||
// 对每个非环境光计算基于距离的衰减与简单高光
|
// 对每个非环境光源计算物理式衰减 + 漫反射 + 简单高光 + 辉光(若启用)
|
||||||
for (int i = 0; i < uLightCount; ++i) {
|
for (int i = 0; i < uLightCount; ++i) {
|
||||||
if (uLightsIsAmbient[i] == 1) continue;
|
if (uLightsIsAmbient[i] == 1) continue;
|
||||||
|
|
||||||
vec2 toLight = uLightsPos[i] - vWorldPos;
|
vec2 toLight2 = uLightsPos[i] - vWorldPos;
|
||||||
float dist = length(toLight);
|
float dist = length(toLight2);
|
||||||
// 标准物理式衰减
|
// 物理风格衰减
|
||||||
float attenuation = ATT_CONST / (ATT_CONST + ATT_LINEAR * dist + ATT_QUAD * dist * dist);
|
float attenuation = ATT_CONST / (ATT_CONST + ATT_LINEAR * dist + ATT_QUAD * dist * dist);
|
||||||
|
|
||||||
// 强度受光源强度和衰减影响
|
|
||||||
float radiance = uLightsIntensity[i] * attenuation;
|
float radiance = uLightsIntensity[i] * attenuation;
|
||||||
|
|
||||||
// 漫反射:在纯2D情景下,法线与视线近似固定(Z向),
|
// 漫反射(在二维中基于距离模拟衰减的明暗)
|
||||||
// 所以漫反射对所有片元是恒定的。我们用一个基于距离的柔和因子来模拟明暗变化。
|
// 使用更平滑的距离曲线:max(0, 1 - (dist / (radiusApprox)))
|
||||||
float diffuseFactor = clamp(1.0 - (dist * 0.0015), 0.0, 1.0); // 通过调节常数控制半径感觉
|
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;
|
vec3 diff = uLightsColor[i] * radiance * diffuseFactor;
|
||||||
lighting += diff;
|
lighting += diff;
|
||||||
|
|
||||||
// 简单高光(基于视向与反射的大致模拟,产生亮点)
|
// 简单高光(在 2D 中模拟亮点)
|
||||||
vec3 lightDir3 = normalize(vec3(toLight, 0.0));
|
|
||||||
vec3 viewDir = vec3(0.0, 0.0, 1.0);
|
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 normal = vec3(0.0, 0.0, 1.0);
|
||||||
vec3 reflectDir = reflect(-lightDir3, normal);
|
vec3 reflectDir = reflect(-lightDir3, normal);
|
||||||
float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 16.0); // 16 为高光粗糙度,可调
|
float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
|
||||||
float specIntensity = 0.2; // 高光强度系数
|
float specIntensity = 0.25;
|
||||||
specularAccum += uLightsColor[i] * radiance * specFactor * specIntensity;
|
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;
|
||||||
|
|
||||||
// 将光照应用到基础颜色
|
// 将辉光作为屏幕加色(加法混合),然后再做一次 tone map 以稳定输出
|
||||||
vec3 finalColor = baseColor * totalLighting;
|
finalColor += glowAccum;
|
||||||
|
finalColor = toneMap(finalColor);
|
||||||
|
|
||||||
// 支持简单混合模式(保留原有行为)
|
// 支持简单的 blend 模式(保留已有行为)
|
||||||
if (uBlendMode == 1) finalColor = tex.rgb + uColor.rgb;
|
if (uBlendMode == 1) finalColor = tex.rgb + uColor.rgb;
|
||||||
else if (uBlendMode == 2) 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);
|
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);
|
com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i);
|
||||||
if (!l.isEnabled()) continue;
|
if (!l.isEnabled()) continue;
|
||||||
|
|
||||||
// 环境光的 position 在 shader 中不会用于距离计算,但我们也上传(安全)
|
// 基础属性
|
||||||
setUniformVec2Internal(sp, "uLightsPos[" + idx + "]", l.isAmbient() ? new org.joml.Vector2f(0f, 0f) : l.getPosition());
|
setUniformVec2Internal(sp, "uLightsPos[" + idx + "]", l.isAmbient() ? new org.joml.Vector2f(0f, 0f) : l.getPosition());
|
||||||
setUniformVec3Internal(sp, "uLightsColor[" + idx + "]", l.getColor());
|
setUniformVec3Internal(sp, "uLightsColor[" + idx + "]", l.getColor());
|
||||||
setUniformFloatInternal(sp, "uLightsIntensity[" + idx + "]", l.getIntensity());
|
setUniformFloatInternal(sp, "uLightsIntensity[" + idx + "]", l.getIntensity());
|
||||||
setUniformIntInternal(sp, "uLightsIsAmbient[" + idx + "]", l.isAmbient() ? 1 : 0);
|
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++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,9 +336,15 @@ public final class ModelRender {
|
|||||||
for (int i = idx; i < 8; i++) {
|
for (int i = idx; i < 8; i++) {
|
||||||
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
|
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
|
||||||
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
|
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
|
||||||
// color/pos 不严格必要,但清零更稳健
|
|
||||||
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
|
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
|
||||||
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(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;
|
if (!light.isEnabled()) continue;
|
||||||
|
|
||||||
// 绘制光源位置
|
// 绘制光源位置
|
||||||
com.chuangzhou.vivid2D.render.util.BufferBuilder bb =
|
BufferBuilder bb =
|
||||||
new com.chuangzhou.vivid2D.render.util.BufferBuilder(1 * 4);
|
new BufferBuilder(1 * 4);
|
||||||
bb.begin(GL11.GL_POINTS, 1);
|
bb.begin(GL11.GL_POINTS, 1);
|
||||||
bb.vertex(light.getPosition().x, light.getPosition().y, 0.5f, 0.5f);
|
bb.vertex(light.getPosition().x, light.getPosition().y, 0.5f, 0.5f);
|
||||||
bb.end();
|
bb.end();
|
||||||
@@ -569,7 +619,7 @@ public final class ModelRender {
|
|||||||
*/
|
*/
|
||||||
private static void drawCircleColliderWire(Vector2f center, float radius) {
|
private static void drawCircleColliderWire(Vector2f center, float radius) {
|
||||||
int segments = Math.max(8, CIRCLE_SEGMENTS);
|
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);
|
bb.begin(GL11.GL_LINE_LOOP, segments);
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
double ang = 2.0 * Math.PI * i / segments;
|
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) {
|
private static void drawRectangleColliderWire(Vector2f center, float width, float height) {
|
||||||
float halfW = width / 2.0f;
|
float halfW = width / 2.0f;
|
||||||
float halfH = height / 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.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);
|
||||||
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;
|
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 com.chuangzhou.vivid2D.render.model.util.*;
|
||||||
import org.joml.Matrix3f;
|
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.GL11;
|
||||||
import org.lwjgl.opengl.GL15;
|
import org.lwjgl.opengl.GL15;
|
||||||
import org.lwjgl.opengl.GL20;
|
import org.lwjgl.opengl.GL20;
|
||||||
@@ -21,6 +20,7 @@ import java.nio.FloatBuffer;
|
|||||||
* bb.end(); // 立即绘制并 cleanup
|
* bb.end(); // 立即绘制并 cleanup
|
||||||
*
|
*
|
||||||
* 设计原则:简单、可靠、方便把临时多顶点数据提交到 GPU。
|
* 设计原则:简单、可靠、方便把临时多顶点数据提交到 GPU。
|
||||||
|
* @author tzdwindows 7
|
||||||
*/
|
*/
|
||||||
public class BufferBuilder {
|
public class BufferBuilder {
|
||||||
private static final int COMPONENTS_PER_VERTEX = 4; // x,y,u,v
|
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.io.Serializable;
|
||||||
import java.util.ArrayList;
|
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.Vector2f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LightSource 的序列化数据类
|
* LightSource 的序列化数据类(扩展:包含辉光/Glow 的序列化字段)
|
||||||
* @author tzdwindows 7
|
* @author tzdwindows 7
|
||||||
*/
|
*/
|
||||||
public class LightSourceData implements Serializable {
|
public class LightSourceData implements Serializable {
|
||||||
@@ -21,6 +22,13 @@ public class LightSourceData implements Serializable {
|
|||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean isAmbient;
|
private boolean isAmbient;
|
||||||
|
|
||||||
|
// ======= 辉光(Glow)相关序列化字段 =======
|
||||||
|
private boolean isGlow;
|
||||||
|
private String glowDirection; // 使用字符串格式存储 Vector2f
|
||||||
|
private float glowIntensity;
|
||||||
|
private float glowRadius;
|
||||||
|
private float glowAmount;
|
||||||
|
|
||||||
// 默认构造器
|
// 默认构造器
|
||||||
public LightSourceData() {
|
public LightSourceData() {
|
||||||
this.id = "light_" + System.currentTimeMillis();
|
this.id = "light_" + System.currentTimeMillis();
|
||||||
@@ -29,6 +37,13 @@ public class LightSourceData implements Serializable {
|
|||||||
this.intensity = 1.0f;
|
this.intensity = 1.0f;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
this.isAmbient = false;
|
this.isAmbient = false;
|
||||||
|
|
||||||
|
// 默认辉光值
|
||||||
|
this.isGlow = false;
|
||||||
|
this.glowDirection = "0,0";
|
||||||
|
this.glowIntensity = 0.0f;
|
||||||
|
this.glowRadius = 50.0f;
|
||||||
|
this.glowAmount = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 LightSource 对象构造
|
// 从 LightSource 对象构造
|
||||||
@@ -41,6 +56,13 @@ public class LightSourceData implements Serializable {
|
|||||||
this.intensity = light.getIntensity();
|
this.intensity = light.getIntensity();
|
||||||
this.enabled = light.isEnabled();
|
this.enabled = light.isEnabled();
|
||||||
this.isAmbient = light.isAmbient();
|
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;
|
LightSource light;
|
||||||
if (isAmbient) {
|
if (isAmbient) {
|
||||||
|
// 使用环境光构造器
|
||||||
light = new LightSource(LightSource.vector3fToColor(col), intensity);
|
light = new LightSource(LightSource.vector3fToColor(col), intensity);
|
||||||
} else {
|
} 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.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;
|
return light;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +112,13 @@ public class LightSourceData implements Serializable {
|
|||||||
copy.intensity = this.intensity;
|
copy.intensity = this.intensity;
|
||||||
copy.enabled = this.enabled;
|
copy.enabled = this.enabled;
|
||||||
copy.isAmbient = this.isAmbient;
|
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;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +202,49 @@ public class LightSourceData implements Serializable {
|
|||||||
isAmbient = ambient;
|
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
|
* 设置位置为 Vector2f
|
||||||
@@ -181,6 +274,20 @@ public class LightSourceData implements Serializable {
|
|||||||
return stringToVector3f(color);
|
return stringToVector3f(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置辉光方向(Vector2f)
|
||||||
|
*/
|
||||||
|
public void setGlowDirection(Vector2f dir) {
|
||||||
|
this.glowDirection = SaveVector2f.toString(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取辉光方向为 Vector2f
|
||||||
|
*/
|
||||||
|
public Vector2f getGlowDirectionAsVector() {
|
||||||
|
return SaveVector2f.fromString(glowDirection);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "LightSourceData{" +
|
return "LightSourceData{" +
|
||||||
@@ -190,6 +297,11 @@ public class LightSourceData implements Serializable {
|
|||||||
", intensity=" + intensity +
|
", intensity=" + intensity +
|
||||||
", enabled=" + enabled +
|
", enabled=" + enabled +
|
||||||
", isAmbient=" + isAmbient +
|
", 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 com.chuangzhou.vivid2D.render.model.util.*;
|
||||||
import org.joml.Vector2f;
|
import org.joml.Vector2f;
|
||||||
import org.slf4j.Logger;
|
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 {
|
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;
|
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.*;
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 光源系统
|
||||||
|
* @author tzdwindows 7
|
||||||
|
*/
|
||||||
public class LightSource {
|
public class LightSource {
|
||||||
private Vector2f position;
|
private Vector2f position;
|
||||||
private Vector3f color;
|
private Vector3f color;
|
||||||
@@ -12,6 +16,13 @@ public class LightSource {
|
|||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
private boolean isAmbient = false; // 是否为环境光
|
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) {
|
public LightSource(Vector2f pos, Color color, float intensity) {
|
||||||
this.position = pos;
|
this.position = pos;
|
||||||
this.color = colorToVector3f(color);
|
this.color = colorToVector3f(color);
|
||||||
@@ -26,6 +37,19 @@ public class LightSource {
|
|||||||
this.isAmbient = true;
|
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) {
|
public static Vector3f colorToVector3f(Color color) {
|
||||||
if (color == null) return new Vector3f(1, 1, 1);
|
if (color == null) return new Vector3f(1, 1, 1);
|
||||||
return new Vector3f(
|
return new Vector3f(
|
||||||
@@ -58,4 +82,22 @@ public class LightSource {
|
|||||||
// 判断是否为环境光
|
// 判断是否为环境光
|
||||||
public boolean isAmbient() { return isAmbient; }
|
public boolean isAmbient() { return isAmbient; }
|
||||||
public void setAmbient(boolean ambient) { this.isAmbient = ambient; }
|
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