From fb1db942edd290a668713455ca904b88463009bc Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sun, 12 Oct 2025 08:16:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor(model):=E9=87=8D=E6=9E=84=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=95=B0=E6=8D=AE=E5=8C=85=E7=BB=93=E6=9E=84=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=85=89=E6=BA=90=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 AnimationLayerData 类从 util 包移动到 data 包 - 将 BufferBuilder 类从 util 包移动到 buffer 包并更新包引用 - 为 LightSource 类添加辉光(Glow)支持及相关字段 - 扩展 LightSourceData 序列化类以包含辉光相关字段 - 新增 MeshData 类用于网格数据的序列化- 更新 Model2D 和 ModelData 的包引用以适应新的类结构 - 移除 ModelData 中重复的内部类定义,统一使用 data 包中的类- 为多个类添加作者信息注解 --- .../vivid2D/render/ModelRender.java | 132 ++- .../vivid2D/render/model/Model2D.java | 2 + .../model/{util => buffer}/BufferBuilder.java | 4 +- .../{util => data}/AnimationLayerData.java | 5 +- .../model/{util => data}/LightSourceData.java | 124 ++- .../vivid2D/render/model/data/MeshData.java | 55 ++ .../render/model/{ => data}/ModelData.java | 803 +----------------- .../model/{util => data}/ModelMetadata.java | 2 +- .../vivid2D/render/model/data/PartData.java | 355 ++++++++ .../render/model/data/TextureData.java | 412 +++++++++ .../render/model/util/LightSource.java | 44 +- 11 files changed, 1087 insertions(+), 851 deletions(-) rename src/main/java/com/chuangzhou/vivid2D/render/model/{util => buffer}/BufferBuilder.java (97%) rename src/main/java/com/chuangzhou/vivid2D/render/model/{util => data}/AnimationLayerData.java (96%) rename src/main/java/com/chuangzhou/vivid2D/render/model/{util => data}/LightSourceData.java (56%) create mode 100644 src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java rename src/main/java/com/chuangzhou/vivid2D/render/model/{ => data}/ModelData.java (52%) rename src/main/java/com/chuangzhou/vivid2D/render/model/{util => data}/ModelMetadata.java (99%) create mode 100644 src/main/java/com/chuangzhou/vivid2D/render/model/data/PartData.java create mode 100644 src/main/java/com/chuangzhou/vivid2D/render/model/data/TextureData.java diff --git a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java index 42930f9..058724a 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java @@ -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); diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/Model2D.java b/src/main/java/com/chuangzhou/vivid2D/render/model/Model2D.java index 8eadc03..f67cf5d 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/Model2D.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/Model2D.java @@ -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; diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/BufferBuilder.java b/src/main/java/com/chuangzhou/vivid2D/render/model/buffer/BufferBuilder.java similarity index 97% rename from src/main/java/com/chuangzhou/vivid2D/render/model/util/BufferBuilder.java rename to src/main/java/com/chuangzhou/vivid2D/render/model/buffer/BufferBuilder.java index 7ee2e73..fbb77e0 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/BufferBuilder.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/buffer/BufferBuilder.java @@ -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 diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/AnimationLayerData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/AnimationLayerData.java similarity index 96% rename from src/main/java/com/chuangzhou/vivid2D/render/model/util/AnimationLayerData.java rename to src/main/java/com/chuangzhou/vivid2D/render/model/data/AnimationLayerData.java index db577e5..f4d1f9b 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/AnimationLayerData.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/AnimationLayerData.java @@ -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; diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSourceData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/LightSourceData.java similarity index 56% rename from src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSourceData.java rename to src/main/java/com/chuangzhou/vivid2D/render/model/data/LightSourceData.java index 51d95e7..0fd5caa 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSourceData.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/LightSourceData.java @@ -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 + '}'; } -} \ No newline at end of file +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java new file mode 100644 index 0000000..01da532 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java @@ -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; + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/ModelData.java similarity index 52% rename from src/main/java/com/chuangzhou/vivid2D/render/model/ModelData.java rename to src/main/java/com/chuangzhou/vivid2D/render/model/data/ModelData.java index 3d6c2f0..99b988d 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelData.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/ModelData.java @@ -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 meshNames; - public Map userData; - - // 保存变形器数据 - public List deformers; - - // 保存液化笔划数据(可保存多个笔划) - public List 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 map = new HashMap<>(); - d.serialization(map); // 让变形器把自己的状态写入 map - dd.properties = map; - this.deformers.add(dd); - } catch (Exception e) { - // 忽略单个变形器序列化错误,避免整个保存失败 - e.printStackTrace(); - } - } - - // 尝试通过反射收集液化笔划数据(兼容性:如果 ModelPart 没有对应 API,则跳过) - try { - // 期望的方法签名: public List 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 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 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 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 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 { diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/ModelMetadata.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/ModelMetadata.java similarity index 99% rename from src/main/java/com/chuangzhou/vivid2D/render/model/util/ModelMetadata.java rename to src/main/java/com/chuangzhou/vivid2D/render/model/data/ModelMetadata.java index 9683976..4a98b6a 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/ModelMetadata.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/ModelMetadata.java @@ -1,4 +1,4 @@ -package com.chuangzhou.vivid2D.render.model.util; +package com.chuangzhou.vivid2D.render.model.data; import org.joml.Vector2f; diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/data/PartData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/PartData.java new file mode 100644 index 0000000..d4b9500 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/PartData.java @@ -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 meshNames; + public Map userData; + + // 保存变形器数据 + public List deformers; + + // 保存液化笔划数据(可保存多个笔划) + public List 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 map = new HashMap<>(); + d.serialization(map); // 让变形器把自己的状态写入 map + dd.properties = map; + this.deformers.add(dd); + } catch (Exception e) { + // 忽略单个变形器序列化错误,避免整个保存失败 + e.printStackTrace(); + } + } + + // 尝试通过反射收集液化笔划数据(兼容性:如果 ModelPart 没有对应 API,则跳过) + try { + // 期望的方法签名: public List 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 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 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 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; + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/data/TextureData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/TextureData.java new file mode 100644 index 0000000..37b6473 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/TextureData.java @@ -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 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") + + '}'; + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSource.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSource.java index d1733c5..c7b8991 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSource.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/LightSource.java @@ -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; } -} \ No newline at end of file + + // ---- 辉光相关的 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; } +}