feat(model): 实现动画层数据序列化与纹理管理增强

- 添加 AnimationLayerData 类用于动画层的序列化支持- 增强 Model2D 的 addTexture 方法,添加空值检查和重复纹理处理
- 在 ModelData 中添加动画层序列化与反序列化逻辑
- 扩展 TextureData 结构以支持完整纹理参数和元数据- 改进纹理反序列化过程,添加错误处理和后备纹理创建- 更新模型测试用例以验证新功能和修复的问题
- 优化网格序列化逻辑,避免重复序列化相同网格- 添加日志记录支持以提高调试能力

重要
- 完全实现了模型的保存和加载(贴图待测试)
This commit is contained in:
tzdwindows 7
2025-10-08 21:02:46 +08:00
parent 424c00ede9
commit 22af92cd84
6 changed files with 1773 additions and 24 deletions

View File

@@ -168,7 +168,23 @@ public class Model2D {
// ==================== 纹理管理 ====================
public void addTexture(Texture texture) {
textures.put(texture.getName(), texture);
if (texture == null) {
throw new IllegalArgumentException("Texture cannot be null");
}
String textureName = texture.getName();
if (textureName == null || textureName.trim().isEmpty()) {
throw new IllegalArgumentException("Texture name cannot be null or empty");
}
if (textures.containsKey(textureName)) {
Texture oldTexture = textures.get(textureName);
if (oldTexture != null && oldTexture != texture) {
oldTexture.dispose();
}
}
textures.put(textureName, texture);
}
public Texture getTexture(String name) {
@@ -291,6 +307,14 @@ public class Model2D {
}
}
/**
* 设置动画层列表(用于反序列化)
*/
public void setAnimationLayers(List<AnimationLayer> animationLayers) {
this.animationLayers.clear();
this.animationLayers.addAll(animationLayers);
}
/**
* 保存模型到压缩文件
*/

View File

@@ -2,6 +2,8 @@ package com.chuangzhou.vivid2D.render.model;
import com.chuangzhou.vivid2D.render.model.util.*;
import org.joml.Vector2f;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.*;
@@ -15,6 +17,7 @@ import java.util.zip.GZIPOutputStream;
* @author tzdwindows 7
*/
public class ModelData implements Serializable {
private static Logger logger = LoggerFactory.getLogger(ModelData.class);
private static final long serialVersionUID = 1L;
// ==================== 模型元数据 ====================
@@ -32,6 +35,7 @@ public class ModelData implements Serializable {
private List<TextureData> textures;
private List<ParameterData> parameters;
private List<AnimationData> animations;
private List<AnimationLayerData> animationLayers;
// ==================== 模型设置 ====================
private Vector2f pivotPoint;
@@ -56,6 +60,7 @@ public class ModelData implements Serializable {
this.textures = new ArrayList<>();
this.parameters = new ArrayList<>();
this.animations = new ArrayList<>();
this.animationLayers = new ArrayList<>();
this.pivotPoint = new Vector2f();
this.unitsPerMeter = 100.0f; // 默认100单位/米
@@ -99,6 +104,9 @@ public class ModelData implements Serializable {
// 序列化参数
serializeParameters(model);
// 序列化动画层
serializeAnimationLayers(model);
lastModifiedTime = System.currentTimeMillis();
}
@@ -111,8 +119,22 @@ public class ModelData implements Serializable {
private void serializeMeshes(Model2D model) {
meshes.clear();
// 首先序列化模型级别的所有网格
for (Mesh2D mesh : model.getMeshes()) {
meshes.add(new MeshData(mesh));
if (!meshExists(mesh.getName())) {
meshes.add(new MeshData(mesh));
}
}
// 然后序列化所有部件中的网格
for (ModelPart part : model.getParts()) {
for (Mesh2D mesh : part.getMeshes()) {
// 检查是否已经序列化过(避免重复)
if (!meshExists(mesh.getName())) {
meshes.add(new MeshData(mesh));
}
}
}
}
@@ -130,6 +152,22 @@ public class ModelData implements Serializable {
}
}
private void serializeAnimationLayers(Model2D model) {
animationLayers.clear();
for (AnimationLayer layer : model.getAnimationLayers()) {
animationLayers.add(new AnimationLayerData(layer));
}
}
private void deserializeAnimationLayers(Model2D model) {
List<AnimationLayer> layers = new ArrayList<>();
for (AnimationLayerData layerData : animationLayers) {
AnimationLayer layer = layerData.toAnimationLayer();
layers.add(layer);
}
model.setAnimationLayers(layers);
}
/**
* 反序列化到Model2D对象
*/
@@ -147,6 +185,11 @@ public class ModelData implements Serializable {
// 先创建所有纹理
Map<String, Texture> textureMap = deserializeTextures();
// === 关键修复:将纹理添加到模型中 ===
for (Texture texture : textureMap.values()) {
model.addTexture(texture);
}
// 然后创建所有网格(依赖纹理)
Map<String, Mesh2D> meshMap = deserializeMeshes(textureMap);
@@ -156,32 +199,75 @@ public class ModelData implements Serializable {
// 最后创建参数
deserializeParameters(model);
// 创建动画层
deserializeAnimationLayers(model);
return model;
}
private Map<String, Texture> deserializeTextures() {
Map<String, Texture> textureMap = new HashMap<>();
for (TextureData textureData : textures) {
Texture texture = textureData.toTexture();
textureMap.put(texture.getName(), texture);
try {
Texture texture = textureData.toTexture();
if (texture != null) {
textureMap.put(texture.getName(), texture);
}
} catch (Exception e) {
logger.error("Error creating texture '{}': {}", textureData.name, e.getMessage());
e.printStackTrace();
Texture fallbackTexture = createFallbackTexture(textureData.name, textureData.width, textureData.height);
if (fallbackTexture != null) {
textureMap.put(textureData.name, fallbackTexture);
}
}
}
return textureMap;
}
/**
* 创建后备纹理
*/
private Texture createFallbackTexture(String name, int width, int height) {
try {
int color;
if (name.contains("body")) {
color = 0xFFFF0000;
} else if (name.contains("head")) {
color = 0xFF00FF00;
} else if (name.contains("checker")) {
return Texture.createCheckerboard(name + "_fallback", width, height, 16, 0xFFFFFFFF, 0xFF0000FF);
} else {
color = 0xFF0000FF;
}
return Texture.createSolidColor(name + "_fallback", width, height, color);
} catch (Exception e) {
System.err.println("Failed to create fallback texture: " + e.getMessage());
return null;
}
}
private Map<String, Mesh2D> deserializeMeshes(Map<String, Texture> textureMap) {
Map<String, Mesh2D> meshMap = new HashMap<>();
for (MeshData meshData : meshes) {
Mesh2D mesh = meshData.toMesh2D();
try {
Mesh2D mesh = meshData.toMesh2D();
// 设置纹理
if (meshData.textureName != null) {
Texture texture = textureMap.get(meshData.textureName);
if (texture != null) {
mesh.setTexture(texture);
// 设置纹理
if (meshData.textureName != null) {
Texture texture = textureMap.get(meshData.textureName);
if (texture != null) {
mesh.setTexture(texture);
} else {
logger.error("Texture not found for mesh '{}': {}", meshData.name, meshData.textureName);
}
}
meshMap.put(mesh.getName(), mesh);
} catch (Exception e) {
logger.error("Error creating mesh '{}': {}", meshData.name, e.getMessage());
}
meshMap.put(mesh.getName(), mesh);
}
return meshMap;
}
@@ -368,7 +454,6 @@ public class ModelData implements Serializable {
copy.creationTime = System.currentTimeMillis();
copy.lastModifiedTime = copy.creationTime;
// 深拷贝集合
for (PartData part : this.parts) {
copy.parts.add(part.copy());
}
@@ -384,6 +469,9 @@ public class ModelData implements Serializable {
for (AnimationData anim : this.animations) {
copy.animations.add(anim.copy());
}
for (AnimationLayerData layer : this.animationLayers) {
copy.animationLayers.add(layer.copy());
}
copy.pivotPoint = new Vector2f(this.pivotPoint);
copy.unitsPerMeter = this.unitsPerMeter;
@@ -591,21 +679,368 @@ public class ModelData implements Serializable {
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() {}
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() {
Texture texture = new Texture(name, width, height, format);
// 注意这里需要处理imageData的加载
// 实际项目中可能需要从文件路径加载图像数据
return texture;
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() {
@@ -616,8 +1051,25 @@ public class ModelData implements Serializable {
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") +
'}';
}
}
/**
@@ -750,6 +1202,11 @@ public class ModelData implements Serializable {
public Map<String, String> getUserData() { return userData; }
public void setUserData(Map<String, String> userData) { this.userData = userData; }
public List<AnimationLayerData> getAnimationLayers() { return animationLayers; }
public void setAnimationLayers(List<AnimationLayerData> animationLayers) {
this.animationLayers = animationLayers;
}
// ==================== Object方法 ====================
@Override

View File

@@ -0,0 +1,167 @@
package com.chuangzhou.vivid2D.render.model.util;
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 AnimationLayerData implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public float weight;
public boolean enabled;
public AnimationLayer.BlendMode blendMode;
public int priority;
public float playbackSpeed;
public boolean looping;
public Map<String, AnimationTrackData> tracks;
public List<String> clipNames;
public AnimationLayerData() {
this.tracks = new HashMap<>();
this.clipNames = new ArrayList<>();
}
public AnimationLayerData(AnimationLayer layer) {
this();
this.name = layer.getName();
this.weight = layer.getWeight();
this.enabled = layer.isEnabled();
this.blendMode = layer.getBlendMode();
this.priority = layer.getPriority();
this.playbackSpeed = layer.getPlaybackSpeed();
this.looping = layer.isLooping();
// 序列化轨道
for (AnimationLayer.AnimationTrack track : layer.getTracks().values()) {
this.tracks.put(track.getParameterId(), new AnimationTrackData(track));
}
// 序列化剪辑名称(剪辑对象本身需要单独序列化)
for (AnimationClip clip : layer.getClips()) {
this.clipNames.add(clip.getName());
}
}
public AnimationLayer toAnimationLayer() {
AnimationLayer layer = new AnimationLayer(name, weight);
layer.setEnabled(enabled);
layer.setBlendMode(blendMode);
layer.setPriority(priority);
layer.setPlaybackSpeed(playbackSpeed);
layer.setLooping(looping);
// 反序列化轨道
for (AnimationTrackData trackData : tracks.values()) {
AnimationLayer.AnimationTrack track = trackData.toAnimationTrack();
layer.getTracks().put(track.getParameterId(), track);
}
// 注意:剪辑对象需要在外部设置
return layer;
}
public AnimationLayerData copy() {
AnimationLayerData copy = new AnimationLayerData();
copy.name = this.name;
copy.weight = this.weight;
copy.enabled = this.enabled;
copy.blendMode = this.blendMode;
copy.priority = this.priority;
copy.playbackSpeed = this.playbackSpeed;
copy.looping = this.looping;
copy.tracks = new HashMap<>();
for (Map.Entry<String, AnimationTrackData> entry : this.tracks.entrySet()) {
copy.tracks.put(entry.getKey(), entry.getValue().copy());
}
copy.clipNames = new ArrayList<>(this.clipNames);
return copy;
}
/**
* 动画轨道数据
*/
public static class AnimationTrackData implements Serializable {
private static final long serialVersionUID = 1L;
public String parameterId;
public boolean enabled;
public AnimationLayer.InterpolationType interpolation;
public List<KeyframeData> keyframes;
public AnimationTrackData() {
this.keyframes = new ArrayList<>();
}
public AnimationTrackData(AnimationLayer.AnimationTrack track) {
this();
this.parameterId = track.getParameterId();
this.enabled = track.isEnabled();
this.interpolation = track.getInterpolation();
// 序列化关键帧
for (AnimationLayer.Keyframe keyframe : track.getKeyframes()) {
this.keyframes.add(new KeyframeData(keyframe));
}
}
public AnimationLayer.AnimationTrack toAnimationTrack() {
AnimationLayer.AnimationTrack track = new AnimationLayer.AnimationTrack(parameterId);
track.setEnabled(enabled);
track.setInterpolation(interpolation);
// 反序列化关键帧
for (KeyframeData kfData : keyframes) {
track.addKeyframe(kfData.time, kfData.value, kfData.interpolation);
}
return track;
}
public AnimationTrackData copy() {
AnimationTrackData copy = new AnimationTrackData();
copy.parameterId = this.parameterId;
copy.enabled = this.enabled;
copy.interpolation = this.interpolation;
copy.keyframes = new ArrayList<>();
for (KeyframeData kf : this.keyframes) {
copy.keyframes.add(kf.copy());
}
return copy;
}
}
/**
* 关键帧数据(重用现有的 KeyframeData
*/
public static class KeyframeData implements Serializable {
private static final long serialVersionUID = 1L;
public float time;
public float value;
public AnimationLayer.InterpolationType interpolation;
public KeyframeData() {}
public KeyframeData(AnimationLayer.Keyframe keyframe) {
this.time = keyframe.getTime();
this.value = keyframe.getValue();
this.interpolation = keyframe.getInterpolation();
}
public KeyframeData copy() {
KeyframeData copy = new KeyframeData();
copy.time = this.time;
copy.value = this.value;
copy.interpolation = this.interpolation;
return copy;
}
}
}

View File

@@ -7,8 +7,11 @@ import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import org.lwjgl.stb.STBImage;
import org.lwjgl.stb.STBImageWrite;
import org.lwjgl.system.MemoryUtil;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -46,6 +49,9 @@ public class Texture {
private static final Map<String, Texture> TEXTURE_CACHE = new HashMap<>();
private static boolean openGLChecked = false;
// ==================== 像素数据缓存支持 ====================
private byte[] pixelDataCache = null;
// ==================== 枚举定义 ====================
public enum TextureFormat {
@@ -124,6 +130,14 @@ public class Texture {
// ==================== 构造器 ====================
// ==================== STB 图像加载 ====================
static {
STBImage.stbi_set_flip_vertically_on_load(true);
}
public Texture(String name, int width, int height, TextureFormat format) {
this(name, width, height, format, TextureType.UNSIGNED_BYTE);
}
@@ -220,9 +234,27 @@ public class Texture {
// 检查OpenGL错误
checkGLError("glTexSubImage2D");
// 缓存像素数据
cachePixelDataFromBuffer(pixelData);
unbind();
}
/**
* 从ByteBuffer缓存像素数据
*/
private void cachePixelDataFromBuffer(ByteBuffer buffer) {
try {
int originalPosition = buffer.position();
pixelDataCache = new byte[buffer.remaining()];
buffer.get(pixelDataCache);
buffer.position(originalPosition);
} catch (Exception e) {
System.err.println("Failed to cache pixel data from buffer: " + e.getMessage());
pixelDataCache = null;
}
}
/**
* 上传整数数组数据到纹理
*/
@@ -413,6 +445,8 @@ public class Texture {
System.err.println("Error disposing texture: " + e.getMessage());
}
pixelDataCache = null;
disposed = true;
TEXTURE_CACHE.values().removeIf(texture -> texture.textureId == this.textureId);
}
@@ -433,8 +467,9 @@ public class Texture {
public static Texture createSolidColor(String name, int width, int height, int rgbaColor) {
int[] pixels = new int[width * height];
java.util.Arrays.fill(pixels, rgbaColor);
return new Texture(name, width, height, TextureFormat.RGBA, pixels);
Texture texture = new Texture(name, width, height, TextureFormat.RGBA, pixels);
texture.ensurePixelDataCached();
return texture;
}
/**
@@ -450,8 +485,9 @@ public class Texture {
pixels[y * width + x] = isColor1 ? color1 : color2;
}
}
return new Texture(name, width, height, TextureFormat.RGBA, pixels);
Texture texture = new Texture(name, width, height, TextureFormat.RGBA, pixels);
texture.ensurePixelDataCached();
return texture;
}
/**
@@ -462,6 +498,471 @@ public class Texture {
new Texture(name, width, height, format));
}
/**
* 从文件系统加载图像数据
*/
public static ImageData loadImageFromFile(String filePath) {
if (filePath == null || filePath.trim().isEmpty()) {
throw new IllegalArgumentException("File path cannot be null or empty");
}
File file = new File(filePath);
if (!file.exists()) {
throw new RuntimeException("Texture file not found: " + filePath);
}
IntBuffer width = MemoryUtil.memAllocInt(1);
IntBuffer height = MemoryUtil.memAllocInt(1);
IntBuffer components = MemoryUtil.memAllocInt(1);
try {
// 使用 STB 加载图像
ByteBuffer imageData = STBImage.stbi_load(filePath, width, height, components, 0);
if (imageData == null) {
String error = STBImage.stbi_failure_reason();
throw new RuntimeException("Failed to load image: " + filePath + " - " + error);
}
// 确定纹理格式
TextureFormat format = getTextureFormat(components.get(0));
return new ImageData(
imageData,
width.get(0),
height.get(0),
format,
filePath
);
} finally {
MemoryUtil.memFree(width);
MemoryUtil.memFree(height);
MemoryUtil.memFree(components);
}
}
/**
* 从文件创建纹理
*/
public static Texture createFromFile(String name, String filePath) {
return createFromFile(name, filePath, TextureFilter.LINEAR, TextureFilter.LINEAR);
}
public static Texture createFromFile(String name, String filePath, TextureFilter minFilter, TextureFilter magFilter) {
ImageData imageData = loadImageFromFile(filePath);
try {
// 创建纹理
Texture texture = new Texture(
name,
imageData.width,
imageData.height,
imageData.format
);
// 上传数据(这会自动缓存)
texture.uploadData(imageData.data);
// 设置纹理参数
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
// 如果是2的幂次方尺寸生成mipmaps
if (texture.isPowerOfTwo(imageData.width) && texture.isPowerOfTwo(imageData.height)) {
texture.generateMipmaps();
}
return texture;
} finally {
// 释放STB图像数据
STBImage.stbi_image_free(imageData.data);
}
}
/**
* 从纹理提取图像数据
*/
public ByteBuffer extractTextureData() {
if (disposed) {
throw new IllegalStateException("Cannot extract data from disposed texture");
}
bind(0);
try {
// 计算数据大小
int dataSize = width * height * format.getComponents();
ByteBuffer pixelData = MemoryUtil.memAlloc(dataSize);
// 从GPU读取纹理数据
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, format.getGLFormat(), type.getGLType(), pixelData);
// 检查OpenGL错误
checkGLError("glGetTexImage");
pixelData.flip();
return pixelData;
} finally {
unbind();
}
}
/**
* 将纹理保存到文件支持PNG、JPG等格式
*/
public boolean saveToFile(String filePath) {
return saveToFile(filePath, "png");
}
public boolean saveToFile(String filePath, String format) {
if (disposed) {
throw new IllegalStateException("Cannot save disposed texture");
}
ByteBuffer pixelData = extractTextureData();
try {
// 根据格式保存图像
boolean success = false;
int components = this.format.getComponents(); // 使用纹理的格式组件数量
int stride = width * components; // 每行的字节数
switch (format.toLowerCase()) {
case "png":
success = STBImageWrite.stbi_write_png(
filePath, width, height, components, pixelData, stride
);
break;
case "jpg":
case "jpeg":
success = STBImageWrite.stbi_write_jpg(
filePath, width, height, components, pixelData, 90 // 质量 0-100
);
break;
case "bmp":
success = STBImageWrite.stbi_write_bmp(filePath, width, height, components, pixelData);
break;
case "tga":
success = STBImageWrite.stbi_write_tga(filePath, width, height, components, pixelData);
break;
default:
throw new IllegalArgumentException("Unsupported image format: " + format);
}
if (!success) {
System.err.println("Failed to save texture to: " + filePath);
return false;
}
return true;
} finally {
MemoryUtil.memFree(pixelData);
}
}
/**
* 检查是否有可用的像素数据缓存
*/
public boolean hasPixelData() {
return this.pixelDataCache != null && this.pixelDataCache.length > 0;
}
/**
* 获取内部像素数据缓存
*/
public byte[] getPixelData() {
if (pixelDataCache == null) {
// 如果没有缓存从GPU提取并缓存
cachePixelDataFromGPU();
}
return pixelDataCache != null ? pixelDataCache.clone() : null;
}
/**
* 设置像素数据缓存
*/
public void setPixelData(byte[] pixelData) {
this.pixelDataCache = pixelData != null ? pixelData.clone() : null;
}
/**
* 从GPU提取像素数据并缓存
*/
private void cachePixelDataFromGPU() {
if (disposed) {
return;
}
try {
ByteBuffer gpuData = extractTextureData();
if (gpuData != null && gpuData.remaining() > 0) {
pixelDataCache = new byte[gpuData.remaining()];
gpuData.get(pixelDataCache);
gpuData.rewind();
MemoryUtil.memFree(gpuData);
}
} catch (Exception e) {
System.err.println("Failed to cache pixel data from GPU: " + e.getMessage());
pixelDataCache = null;
}
}
/**
* 清除像素数据缓存
*/
public void clearPixelDataCache() {
this.pixelDataCache = null;
}
/**
* 确保像素数据缓存可用如果不存在则从GPU提取
*/
public void ensurePixelDataCached() {
if (!hasPixelData()) {
cachePixelDataFromGPU();
}
}
/**
* 获取像素数据缓存大小(字节)
*/
public int getPixelDataCacheSize() {
return pixelDataCache != null ? pixelDataCache.length : 0;
}
/**
* 创建纹理的深拷贝(包括图像数据)
*/
public Texture copy() {
return copy(this.name + "_copy");
}
public Texture copy(String newName) {
if (disposed) {
throw new IllegalStateException("Cannot copy disposed texture");
}
// 确保缓存数据可用
ensurePixelDataCached();
ByteBuffer pixelData = null;
try {
if (hasPixelData()) {
// 使用缓存数据创建新纹理
pixelData = MemoryUtil.memAlloc(pixelDataCache.length);
pixelData.put(pixelDataCache);
pixelData.flip();
} else {
// 回退到GPU提取
pixelData = extractTextureData();
}
// 创建新纹理
Texture copy = new Texture(newName, width, height, format, pixelData);
// 复制纹理参数
copy.setMinFilter(this.minFilter);
copy.setMagFilter(this.magFilter);
copy.setWrapS(this.wrapS);
copy.setWrapT(this.wrapT);
// 复制像素数据缓存
if (hasPixelData()) {
copy.setPixelData(this.pixelDataCache);
}
if (this.mipmapsEnabled) {
copy.generateMipmaps();
}
return copy;
} finally {
if (pixelData != null) {
MemoryUtil.memFree(pixelData);
}
}
}
// ==================== 辅助方法和内部类 ====================
/**
* 根据组件数量确定纹理格式
*/
public static TextureFormat getTextureFormat(int components) {
switch (components) {
case 1: return TextureFormat.RED;
case 2: return TextureFormat.RG;
case 3: return TextureFormat.RGB;
case 4: return TextureFormat.RGBA;
default:
throw new IllegalArgumentException("Unsupported number of components: " + components);
}
}
/**
* 图像数据容器类
*/
public static class ImageData {
public final ByteBuffer data;
public final int width;
public final int height;
public final TextureFormat format;
public final String sourcePath;
public ImageData(ByteBuffer data, int width, int height, TextureFormat format, String sourcePath) {
this.data = data;
this.width = width;
this.height = height;
this.format = format;
this.sourcePath = sourcePath;
}
/**
* 创建图像数据的拷贝
*/
public ImageData copy() {
ByteBuffer copyBuffer = MemoryUtil.memAlloc(data.capacity());
copyBuffer.put(data);
copyBuffer.flip();
data.rewind();
return new ImageData(copyBuffer, width, height, format, sourcePath);
}
/**
* 释放图像数据内存
*/
public void free() {
if (data != null) {
STBImage.stbi_image_free(data);
}
}
}
/**
* 支持的图像格式检查
*/
public static boolean isSupportedImageFormat(String filePath) {
if (filePath == null) return false;
String lowerPath = filePath.toLowerCase();
return lowerPath.endsWith(".png") ||
lowerPath.endsWith(".jpg") ||
lowerPath.endsWith(".jpeg") ||
lowerPath.endsWith(".bmp") ||
lowerPath.endsWith(".tga") ||
lowerPath.endsWith(".psd") ||
lowerPath.endsWith(".gif") ||
lowerPath.endsWith(".hdr") ||
lowerPath.endsWith(".pic");
}
/**
* 获取图像文件信息(不加载完整图像)
*/
public static ImageInfo getImageInfo(String filePath) {
if (!isSupportedImageFormat(filePath)) {
throw new IllegalArgumentException("Unsupported image format: " + filePath);
}
IntBuffer width = MemoryUtil.memAllocInt(1);
IntBuffer height = MemoryUtil.memAllocInt(1);
IntBuffer components = MemoryUtil.memAllocInt(1);
try {
boolean success = STBImage.stbi_info(filePath, width, height, components);
if (!success) {
String error = STBImage.stbi_failure_reason();
throw new RuntimeException("Failed to get image info: " + filePath + " - " + error);
}
return new ImageInfo(
width.get(0),
height.get(0),
components.get(0),
filePath
);
} finally {
MemoryUtil.memFree(width);
MemoryUtil.memFree(height);
MemoryUtil.memFree(components);
}
}
/**
* 图像信息类
*/
public static class ImageInfo {
public final int width;
public final int height;
public final int components;
public final String filePath;
public ImageInfo(int width, int height, int components, String filePath) {
this.width = width;
this.height = height;
this.components = components;
this.filePath = filePath;
}
@Override
public String toString() {
return String.format("ImageInfo{size=%dx%d, components=%d, path='%s'}",
width, height, components, filePath);
}
}
// ==================== 新的静态工厂方法 ====================
/**
* 从字节数组创建纹理
*/
public static Texture createFromBytes(String name, byte[] imageData, TextureFilter minFilter, TextureFilter magFilter) {
ByteBuffer buffer = MemoryUtil.memAlloc(imageData.length);
buffer.put(imageData);
buffer.flip();
// 使用STB从内存加载图像
IntBuffer width = MemoryUtil.memAllocInt(1);
IntBuffer height = MemoryUtil.memAllocInt(1);
IntBuffer components = MemoryUtil.memAllocInt(1);
try {
ByteBuffer pixelData = STBImage.stbi_load_from_memory(buffer, width, height, components, 0);
if (pixelData == null) {
String error = STBImage.stbi_failure_reason();
throw new RuntimeException("Failed to load image from bytes: " + error);
}
TextureFormat format = getTextureFormat(components.get(0));
Texture texture = new Texture(name, width.get(0), height.get(0), format, pixelData);
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
STBImage.stbi_image_free(pixelData);
return texture;
} finally {
MemoryUtil.memFree(buffer);
MemoryUtil.memFree(width);
MemoryUtil.memFree(height);
MemoryUtil.memFree(components);
}
}
// ==================== 工具方法 ====================
/**