feat(render):重构 ModelGLPanel与 ModelRender 并增强渲染功能
- 重构 ModelGLPanel 支持动态尺寸调整和离屏渲染上下文重建 - 添加 GL 上下文任务队列机制,支持线程安全的 OpenGL 操作- 引入 SLF4J 日志系统替换原有 System.out 输出 - 优化像素读取逻辑,支持 ARGB 格式与图像缓冲复用- 增强错误处理与资源清理逻辑,提升稳定性 - 完善 Model2D与 ModelRender 类的文档注释与结构定义 - 新增 TestModelGLPanel 动画示例,展示模型部件控制与物理系统应用
This commit is contained in:
@@ -6,31 +6,56 @@ import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 修复版高性能 OpenGL 渲染面板
|
||||
* vivid2D 模型的 Java 渲染面板
|
||||
*
|
||||
* <p>该类提供了 vivid2D 模型在 Java 环境下的图形渲染功能,
|
||||
* 包含基本的 2D 图形绘制、模型显示和交互操作。</p>
|
||||
*
|
||||
* <p>具体使用示例请参考:{@code com.chuangzhou.vivid2D.test.TestModelGLPanel}</p>
|
||||
*
|
||||
* @author tzdwindows
|
||||
* @version 1.0
|
||||
* @since 2025-10-13
|
||||
* @see com.chuangzhou.vivid2D.test.TestModelGLPanel
|
||||
*/
|
||||
public class ModelGLPanel extends JPanel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ModelGLPanel.class);
|
||||
private final AtomicReference<Model2D> modelRef = new AtomicReference<>();
|
||||
private long windowId;
|
||||
private volatile boolean running = true;
|
||||
private Thread renderThread;
|
||||
private final int width;
|
||||
private final int height;
|
||||
// 改为可变的宽高以支持动态重建离屏上下文缓冲
|
||||
private volatile int width;
|
||||
private volatile int height;
|
||||
|
||||
private BufferedImage currentFrame;
|
||||
private boolean contextInitialized = false;
|
||||
private volatile boolean contextInitialized = false;
|
||||
private final CompletableFuture<Void> contextReady = new CompletableFuture<>();
|
||||
private final String modelPath;
|
||||
|
||||
// 任务队列,用于在 GL 上下文线程执行代码
|
||||
private final BlockingQueue<Runnable> glTaskQueue = new LinkedBlockingQueue<>();
|
||||
private final ExecutorService taskExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private BufferedImage lastFrame = null;
|
||||
private ByteBuffer pixelBuffer = null;
|
||||
private int[] pixelInts = null;
|
||||
private int[] argbInts = null;
|
||||
|
||||
/**
|
||||
* 构造函数:使用模型路径
|
||||
*/
|
||||
@@ -63,6 +88,20 @@ public class ModelGLPanel extends JPanel {
|
||||
|
||||
// 创建渲染线程
|
||||
startRendering();
|
||||
|
||||
this.addComponentListener(new java.awt.event.ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(java.awt.event.ComponentEvent e) {
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
// 忽略无效尺寸或未变化的情况
|
||||
if (w <= 0 || h <= 0) return;
|
||||
if (w == ModelGLPanel.this.width && h == ModelGLPanel.this.height) return;
|
||||
// 调用本类的 resize 方法(会在 GL 上下文线程中执行实际的 GL 更新)
|
||||
|
||||
ModelGLPanel.this.resize(w, h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +117,7 @@ public class ModelGLPanel extends JPanel {
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GL11.GL_TRUE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 4);
|
||||
|
||||
// 创建离屏窗口
|
||||
// 创建离屏窗口(像素尺寸以当前 width/height 为准)
|
||||
windowId = GLFW.glfwCreateWindow(width, height, "Offscreen Render", MemoryUtil.NULL, MemoryUtil.NULL);
|
||||
if (windowId == MemoryUtil.NULL) {
|
||||
throw new Exception("无法创建离屏 OpenGL 上下文");
|
||||
@@ -88,27 +127,34 @@ public class ModelGLPanel extends JPanel {
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
GL.createCapabilities();
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
// 初始化 OpenGL 状态
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
// 检查是否支持多重采样
|
||||
if (GL.getCapabilities().OpenGL13) {
|
||||
GL11.glEnable(GL13.GL_MULTISAMPLE);
|
||||
System.out.println("多重采样已启用");
|
||||
logger.info("多重采样已启用");
|
||||
} else {
|
||||
System.out.println("不支持多重采样,跳过启用");
|
||||
logger.info("不支持多重采样,跳过启用");
|
||||
}
|
||||
|
||||
GL11.glViewport(0, 0, width, height);
|
||||
|
||||
// 按当前宽高分配像素读取缓冲
|
||||
int pixelCount = Math.max(1, width * height);
|
||||
pixelBuffer = MemoryUtil.memAlloc(pixelCount * 4);
|
||||
pixelBuffer.order(ByteOrder.nativeOrder());
|
||||
pixelInts = new int[pixelCount];
|
||||
argbInts = new int[pixelCount];
|
||||
|
||||
ModelRender.initialize();
|
||||
|
||||
contextInitialized = true;
|
||||
|
||||
// 在正确的上下文中加载模型
|
||||
// 在正确的上下文中加载模型(可能会耗时)
|
||||
loadModelInContext();
|
||||
|
||||
// 通知上下文已准备就绪
|
||||
// 标记上下文已初始化并完成通知(只 complete 一次)
|
||||
contextInitialized = true;
|
||||
contextReady.complete(null);
|
||||
}
|
||||
|
||||
@@ -120,10 +166,10 @@ public class ModelGLPanel extends JPanel {
|
||||
if (modelPath != null) {
|
||||
Model2D model = Model2D.loadFromFile(modelPath);
|
||||
modelRef.set(model);
|
||||
System.out.println("模型加载成功: " + modelPath);
|
||||
logger.info("模型加载成功: {}", modelPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("模型加载失败: " + e.getMessage());
|
||||
logger.error("模型加载失败: {}", e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
|
||||
// 创建错误模型或使用默认模型
|
||||
@@ -152,23 +198,28 @@ public class ModelGLPanel extends JPanel {
|
||||
try {
|
||||
createOffscreenContext();
|
||||
|
||||
// 等待上下文就绪后再开始渲染循环
|
||||
// 等待上下文就绪后再开始渲染循环(contextReady 由 createOffscreenContext 完成)
|
||||
contextReady.get();
|
||||
|
||||
// 高性能渲染循环
|
||||
// 确保当前线程一直持有该 GL 上下文(避免在每个任务/帧中重复 makeCurrent)
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
|
||||
final long targetNs = 1_000_000_000L / 60L; // 60 FPS
|
||||
while (running && !GLFW.glfwWindowShouldClose(windowId)) {
|
||||
long start = System.nanoTime();
|
||||
|
||||
processGLTasks();
|
||||
|
||||
renderFrame();
|
||||
|
||||
// 控制帧率
|
||||
try {
|
||||
Thread.sleep(1000 / 60); // 60 FPS
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
long elapsed = System.nanoTime() - start;
|
||||
long sleepNs = targetNs - elapsed;
|
||||
if (sleepNs > 0) {
|
||||
LockSupport.parkNanos(sleepNs);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("渲染线程异常", e);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
@@ -179,6 +230,21 @@ public class ModelGLPanel extends JPanel {
|
||||
renderThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 GL 上下文任务队列
|
||||
*/
|
||||
private void processGLTasks() {
|
||||
Runnable task;
|
||||
while ((task = glTaskQueue.poll()) != null) {
|
||||
try {
|
||||
// 在渲染线程中执行,渲染线程已将上下文设为 current
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
logger.error("执行 GL 任务时出错", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染单帧并读取到 BufferedImage
|
||||
*/
|
||||
@@ -222,10 +288,10 @@ public class ModelGLPanel extends JPanel {
|
||||
readPixelsToImage();
|
||||
|
||||
// 创建错误图像
|
||||
BufferedImage errorImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage errorImage = new BufferedImage(Math.max(1, width), Math.max(1, height), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = errorImage.createGraphics();
|
||||
g2d.setColor(Color.DARK_GRAY);
|
||||
g2d.fillRect(0, 0, width, height);
|
||||
g2d.fillRect(0, 0, errorImage.getWidth(), errorImage.getHeight());
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.drawString("渲染错误: " + errorMessage, 10, 20);
|
||||
g2d.dispose();
|
||||
@@ -246,33 +312,60 @@ public class ModelGLPanel extends JPanel {
|
||||
*/
|
||||
private void readPixelsToImage() {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
|
||||
GL11.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
|
||||
final int w = Math.max(1, this.width);
|
||||
final int h = Math.max(1, this.height);
|
||||
final int pixelCount = w * h;
|
||||
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
// 确保缓冲区大小匹配(可能在 resize 后需要重建)
|
||||
if (pixelBuffer == null || pixelInts == null || pixelInts.length != pixelCount) {
|
||||
if (pixelBuffer != null) {
|
||||
try { MemoryUtil.memFree(pixelBuffer); } catch (Throwable ignored) {}
|
||||
}
|
||||
pixelBuffer = MemoryUtil.memAlloc(pixelCount * 4);
|
||||
pixelBuffer.order(ByteOrder.nativeOrder());
|
||||
pixelInts = new int[pixelCount];
|
||||
argbInts = new int[pixelCount];
|
||||
}
|
||||
|
||||
// 转换像素数据
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int i = (x + (height - y - 1) * width) * 4; // 翻转 Y 轴
|
||||
pixelBuffer.clear();
|
||||
// 从 GPU 读取 RGBA 字节到本地缓冲
|
||||
GL11.glReadPixels(0, 0, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixelBuffer);
|
||||
|
||||
int r = buffer.get(i) & 0xFF;
|
||||
int g = buffer.get(i + 1) & 0xFF;
|
||||
int b = buffer.get(i + 2) & 0xFF;
|
||||
// 以 int 批量读取(依赖本机字节序),然后转换为带 alpha 的 ARGB 并垂直翻转
|
||||
IntBuffer ib = pixelBuffer.asIntBuffer();
|
||||
ib.get(pixelInts, 0, pixelCount);
|
||||
|
||||
int rgb = (r << 16) | (g << 8) | b;
|
||||
image.setRGB(x, y, rgb);
|
||||
// 转换并翻转(RGBA -> ARGB)
|
||||
for (int y = 0; y < h; y++) {
|
||||
int srcRow = (h - y - 1) * w;
|
||||
int dstRow = y * w;
|
||||
for (int x = 0; x < w; x++) {
|
||||
int rgba = pixelInts[srcRow + x];
|
||||
|
||||
// 提取字节(考虑 native order,按 RGBA 存放)
|
||||
int r = (rgba >> 0) & 0xFF;
|
||||
int g = (rgba >> 8) & 0xFF;
|
||||
int b = (rgba >> 16) & 0xFF;
|
||||
int a = (rgba >> 24) & 0xFF;
|
||||
|
||||
// 组合为 ARGB (BufferedImage 使用 ARGB)
|
||||
argbInts[dstRow + x] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用一次 setRGB 写入 BufferedImage(比逐像素 setRGB 快)
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
image.setRGB(0, 0, w, h, argbInts, 0, w);
|
||||
|
||||
currentFrame = image;
|
||||
lastFrame = image;
|
||||
} catch (Exception e) {
|
||||
System.err.println("读取像素数据错误: " + e.getMessage());
|
||||
// 创建错误图像
|
||||
BufferedImage errorImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
logger.error("读取像素数据错误", e);
|
||||
// 创建错误图像(保持原逻辑)
|
||||
BufferedImage errorImage = new BufferedImage(Math.max(1, this.width), Math.max(1, this.height), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = errorImage.createGraphics();
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.fillRect(0, 0, width, height);
|
||||
g2d.fillRect(0, 0, errorImage.getWidth(), errorImage.getHeight());
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.drawString("像素读取失败", 10, 20);
|
||||
g2d.dispose();
|
||||
@@ -284,32 +377,135 @@ public class ModelGLPanel extends JPanel {
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
if (currentFrame != null) {
|
||||
// 绘制当前帧到面板
|
||||
g.drawImage(currentFrame, 0, 0, getWidth(), getHeight(), null);
|
||||
} else {
|
||||
// 显示加载中信息
|
||||
g.setColor(Color.DARK_GRAY);
|
||||
g.fillRect(0, 0, getWidth(), getHeight());
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawString("初始化中...", getWidth() / 2 - 30, getHeight() / 2);
|
||||
}
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
try {
|
||||
// 选择要绘制的图像:优先 currentFrame(最新),其不存在则用 lastFrame(最后成功帧)
|
||||
BufferedImage imgToDraw = currentFrame != null ? currentFrame : lastFrame;
|
||||
|
||||
// 如果模型为空,显示提示
|
||||
if (modelRef.get() == null) {
|
||||
g.setColor(Color.YELLOW);
|
||||
g.drawString("模型未加载", 10, 20);
|
||||
int panelW = getWidth();
|
||||
int panelH = getHeight();
|
||||
|
||||
if (imgToDraw != null) {
|
||||
// 绘制图像并拉伸以适应面板(保留最近一帧,避免闪烁)
|
||||
g2d.drawImage(imgToDraw, 0, 0, panelW, panelH, null);
|
||||
} else {
|
||||
// 没有任何帧时,绘制静态背景(不会频繁切换)
|
||||
g2d.setColor(Color.DARK_GRAY);
|
||||
g2d.fillRect(0, 0, panelW, panelH);
|
||||
}
|
||||
|
||||
// 如果模型为空,显示提示(绘制在最上层)
|
||||
if (modelRef.get() == null) {
|
||||
g2d.setColor(new Color(255, 255, 0, 200));
|
||||
g2d.drawString("模型未加载", 10, 20);
|
||||
}
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 新增:GL 上下文任务执行方法 ==================
|
||||
|
||||
/**
|
||||
* 设置模型(线程安全)
|
||||
* 在 GL 上下文线程上异步执行任务
|
||||
* @param task 要在 GL 上下文线程中执行的任务
|
||||
* @return CompletableFuture 用于获取任务执行结果
|
||||
*/
|
||||
public CompletableFuture<Void> executeInGLContext(Runnable task) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
if (!running) {
|
||||
future.completeExceptionally(new IllegalStateException("渲染线程已停止"));
|
||||
return future;
|
||||
}
|
||||
|
||||
// 等待上下文就绪后再提交任务
|
||||
contextReady.thenRun(() -> {
|
||||
try {
|
||||
// 使用 put 保证任务不会被丢弃,如果队列已满会阻塞调用者直到可入队
|
||||
glTaskQueue.put(() -> {
|
||||
try {
|
||||
task.run();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 GL 上下文线程上异步执行任务并返回结果
|
||||
* @param task 要在 GL 上下文线程中执行的有返回值的任务
|
||||
* @return CompletableFuture 用于获取任务执行结果
|
||||
*/
|
||||
public <T> CompletableFuture<T> executeInGLContext(Callable<T> task) {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
|
||||
if (!running) {
|
||||
future.completeExceptionally(new IllegalStateException("渲染线程已停止"));
|
||||
return future;
|
||||
}
|
||||
|
||||
contextReady.thenRun(() -> {
|
||||
try {
|
||||
glTaskQueue.put(() -> {
|
||||
try {
|
||||
T result = task.call();
|
||||
future.complete(result);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步在 GL 上下文线程上执行任务(会阻塞当前线程直到任务完成)
|
||||
* @param task 要在 GL 上下文线程中执行的任务
|
||||
* @throws Exception 如果任务执行出错
|
||||
*/
|
||||
public void executeInGLContextSync(Runnable task) throws Exception {
|
||||
if (!running) {
|
||||
throw new IllegalStateException("渲染线程已停止");
|
||||
}
|
||||
|
||||
CompletableFuture<Void> future = executeInGLContext(task);
|
||||
future.get(10, TimeUnit.SECONDS); // 设置超时时间
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步在 GL 上下文线程上执行任务并返回结果(会阻塞当前线程直到任务完成)
|
||||
* @param task 要在 GL 上下文线程中执行的有返回值的任务
|
||||
* @return 任务执行结果
|
||||
* @throws Exception 如果任务执行出错或超时
|
||||
*/
|
||||
public <T> T executeInGLContextSync(Callable<T> task) throws Exception {
|
||||
if (!running) {
|
||||
throw new IllegalStateException("渲染线程已停止");
|
||||
}
|
||||
|
||||
CompletableFuture<T> future = executeInGLContext(task);
|
||||
return future.get(10, TimeUnit.SECONDS); // 设置超时时间
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型(线程安全)- 使用新的 GL 上下文执行方法
|
||||
*/
|
||||
public void setModel(Model2D model) {
|
||||
// 等待上下文就绪后再设置模型
|
||||
contextReady.thenRun(() -> {
|
||||
executeInGLContext(() -> {
|
||||
modelRef.set(model);
|
||||
System.out.println("模型已更新");
|
||||
logger.info("模型已更新");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -322,19 +518,51 @@ public class ModelGLPanel extends JPanel {
|
||||
|
||||
/**
|
||||
* 重新设置面板大小
|
||||
*
|
||||
* 说明:当 Swing 面板被放大时,需要同时调整离屏 GLFW 窗口像素大小、GL 视口以及重分配像素读取缓冲,
|
||||
* 否则将把较小分辨率的图像拉伸到更大面板上导致模糊。
|
||||
*/
|
||||
public void resize(int newWidth, int newHeight) {
|
||||
// 更新 Swing 尺寸
|
||||
setPreferredSize(new Dimension(newWidth, newHeight));
|
||||
revalidate();
|
||||
|
||||
// 在渲染线程中更新视口
|
||||
contextReady.thenRun(() -> {
|
||||
// 在 GL 上下文线程中更新离屏窗口与缓冲
|
||||
executeInGLContext(() -> {
|
||||
if (contextInitialized && windowId != 0) {
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
GL11.glViewport(0, 0, newWidth, newHeight);
|
||||
// 更新内部宽高字段
|
||||
this.width = Math.max(1, newWidth);
|
||||
this.height = Math.max(1, newHeight);
|
||||
|
||||
// 重新创建帧缓冲图像
|
||||
// 将离屏 GLFW 窗口也调整为新的像素尺寸
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
GLFW.glfwSetWindowSize(windowId, this.width, this.height);
|
||||
|
||||
// 更新 OpenGL 视口与 ModelRender 的视口
|
||||
GL11.glViewport(0, 0, this.width, this.height);
|
||||
ModelRender.setViewport(this.width, this.height);
|
||||
|
||||
// 重新分配像素读取缓冲区(释放旧的)
|
||||
try {
|
||||
if (pixelBuffer != null) {
|
||||
MemoryUtil.memFree(pixelBuffer);
|
||||
pixelBuffer = null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// 忽略释放错误,继续重分配
|
||||
}
|
||||
int pixelCount = Math.max(1, this.width * this.height);
|
||||
pixelBuffer = MemoryUtil.memAlloc(pixelCount * 4);
|
||||
pixelBuffer.order(ByteOrder.nativeOrder());
|
||||
pixelInts = new int[pixelCount];
|
||||
argbInts = new int[pixelCount];
|
||||
|
||||
// 丢弃当前帧,下一帧会使用新尺寸重新生成
|
||||
currentFrame = null;
|
||||
} else {
|
||||
// 如果还没初始化 GL,上层改变 Swing 大小即可,实际缓冲会在 createOffscreenContext 时按最新宽高创建
|
||||
this.width = Math.max(1, newWidth);
|
||||
this.height = Math.max(1, newHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -358,6 +586,10 @@ public class ModelGLPanel extends JPanel {
|
||||
*/
|
||||
public void dispose() {
|
||||
running = false;
|
||||
|
||||
// 停止任务执行器
|
||||
taskExecutor.shutdown();
|
||||
|
||||
if (renderThread != null) {
|
||||
try {
|
||||
renderThread.join(2000);
|
||||
@@ -369,11 +601,38 @@ public class ModelGLPanel extends JPanel {
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
// 清理 ModelRender
|
||||
try {
|
||||
if (ModelRender.isInitialized()) {
|
||||
ModelRender.cleanup();
|
||||
logger.info("ModelRender 已清理");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("清理 ModelRender 时出错: {}", e.getMessage());
|
||||
}
|
||||
|
||||
if (windowId != 0) {
|
||||
GLFW.glfwDestroyWindow(windowId);
|
||||
try {
|
||||
GLFW.glfwDestroyWindow(windowId);
|
||||
} catch (Throwable ignored) {}
|
||||
windowId = 0;
|
||||
}
|
||||
GLFW.glfwTerminate();
|
||||
System.out.println("OpenGL 资源已清理");
|
||||
|
||||
// 释放像素缓冲
|
||||
try {
|
||||
if (pixelBuffer != null) {
|
||||
MemoryUtil.memFree(pixelBuffer);
|
||||
pixelBuffer = null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("释放 pixelBuffer 时出错: {}", t.getMessage());
|
||||
}
|
||||
|
||||
// 终止 GLFW(注意:如果应用中还有其他 GLFW 窗口,这里会影响它们)
|
||||
try {
|
||||
GLFW.glfwTerminate();
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
logger.info("OpenGL 资源已清理");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,31 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
|
||||
|
||||
/**
|
||||
* 重构后的 ModelRender:更模块化、健壮的渲染子系统
|
||||
* (已修改以应用物理系统,并支持渲染碰撞箱)
|
||||
* @author tzdwindows 7
|
||||
* vivid2D 模型完整渲染系统
|
||||
*
|
||||
* <p>该系统提供了完整的 vivid2D 模型加载、渲染和显示功能,支持多种渲染模式和效果:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>基础模型渲染</li>
|
||||
* <li>光照效果渲染</li>
|
||||
* <li>纹理贴图渲染</li>
|
||||
* <li>模型加载与解析</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>使用示例:</h3>
|
||||
* <ul>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelLoadTest} - 模型加载测试</li>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderLightingTest} - 光照渲染测试</li>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTest} - 基础渲染测试</li>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTest2} - 进阶渲染测试</li>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTextureTest} - 纹理渲染测试</li>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest} - 基础模型测试</li>
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest2} - 进阶模型测试</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author tzdwindows
|
||||
* @version 1.0
|
||||
* @since 2025-10-13
|
||||
*/
|
||||
public final class ModelRender {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ModelRender.class);
|
||||
|
||||
@@ -9,10 +9,28 @@ import org.joml.Matrix3f;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 2D模型核心数据结构
|
||||
* (已修改以配合 ModelRender 的物理系统应用)
|
||||
* 2D 模型核心数据结构
|
||||
*
|
||||
* @author tzdwindows 7
|
||||
* <p>定义 vivid2D 模型系统中的核心数据结构和基础数据类型,包括:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>几何数据:顶点、边、面等基本几何元素</li>
|
||||
* <li>拓扑结构:模型的组织关系和连接信息</li>
|
||||
* <li>属性数据:颜色、纹理坐标、法向量等附加属性</li>
|
||||
* <li>层次结构:模型的父子关系和变换信息</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>主要包含:</h3>
|
||||
* <ul>
|
||||
* <li>基础几何类(点、向量、矩阵)</li>
|
||||
* <li>模型节点和组件类</li>
|
||||
* <li>数据容器和缓冲区</li>
|
||||
* <li>序列化和反序列化支持</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author tzdwindows
|
||||
* @version 1.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public class Model2D {
|
||||
// ==================== 基础属性 ====================
|
||||
|
||||
Reference in New Issue
Block a user