diff --git a/build.gradle b/build.gradle index 923e960..f8d1001 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.commonmark:commonmark:0.24.0' implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1' implementation 'com.google.code.gson:gson:2.8.9' @@ -81,13 +82,16 @@ dependencies { implementation 'com.github.javaparser:javaparser-core:3.25.1' implementation 'com.1stleg:jnativehook:2.1.0' implementation 'org.json:json:20230618' - implementation 'org.lwjgl:lwjgl:3.3.1' - implementation 'org.lwjgl:lwjgl-stb:3.3.3' - implementation 'org.lwjgl:lwjgl-glfw:3.3.1' - implementation 'org.lwjgl:lwjgl-opengl:3.3.1' - implementation 'org.lwjgl:lwjgl:3.3.1:natives-windows' - implementation 'org.lwjgl:lwjgl-glfw:3.3.1:natives-windows' - implementation 'org.lwjgl:lwjgl-opengl:3.3.1:natives-windows' + implementation 'org.lwjgl:lwjgl:3.3.6' + implementation 'org.lwjgl:lwjgl-stb:3.3.6' + implementation 'org.lwjgl:lwjgl-glfw:3.3.6' + implementation 'org.lwjgl:lwjgl-opengl:3.3.6' + implementation 'org.lwjgl:lwjgl-jawt:3.3.5' + + runtimeOnly 'org.lwjgl:lwjgl:3.3.6:natives-windows' + runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.6:natives-windows' + runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.6:natives-windows' + runtimeOnly 'org.lwjgl:lwjgl-stb:3.3.6:natives-windows' implementation 'com.badlogicgames.gdx:gdx:1.12.1' implementation 'org.joml:joml:1.10.7' implementation 'org.bytedeco:javacv-platform:1.5.7' diff --git a/src/main/java/com/chuangzhou/vivid2D/render/ModelGLPanel.java b/src/main/java/com/chuangzhou/vivid2D/render/ModelGLPanel.java new file mode 100644 index 0000000..62e0f96 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/ModelGLPanel.java @@ -0,0 +1,379 @@ +package com.chuangzhou.vivid2D.render; + +import com.chuangzhou.vivid2D.render.model.Model2D; +import org.lwjgl.glfw.*; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.system.MemoryUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 修复版高性能 OpenGL 渲染面板 + */ +public class ModelGLPanel extends JPanel { + + private final AtomicReference modelRef = new AtomicReference<>(); + private long windowId; + private volatile boolean running = true; + private Thread renderThread; + private final int width; + private final int height; + + private BufferedImage currentFrame; + private boolean contextInitialized = false; + private final CompletableFuture contextReady = new CompletableFuture<>(); + private final String modelPath; + + /** + * 构造函数:使用模型路径 + */ + public ModelGLPanel(String modelPath, int width, int height) { + this.modelPath = modelPath; + this.width = width; + this.height = height; + initialize(); + } + + /** + * 构造函数:使用已加载模型 + */ + public ModelGLPanel(Model2D model, int width, int height) { + this.modelPath = null; + this.width = width; + this.height = height; + this.modelRef.set(model); + initialize(); + } + + private void initialize() { + setLayout(new BorderLayout()); + setPreferredSize(new Dimension(width, height)); + + // 初始化 GLFW + if (!GLFW.glfwInit()) { + throw new RuntimeException("无法初始化 GLFW"); + } + + // 创建渲染线程 + startRendering(); + } + + /** + * 创建离屏 OpenGL 上下文 + */ + private void createOffscreenContext() throws Exception { + // 设置窗口提示 + GLFW.glfwDefaultWindowHints(); + GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3); + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GL11.GL_TRUE); + GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 4); + + // 创建离屏窗口 + windowId = GLFW.glfwCreateWindow(width, height, "Offscreen Render", MemoryUtil.NULL, MemoryUtil.NULL); + if (windowId == MemoryUtil.NULL) { + throw new Exception("无法创建离屏 OpenGL 上下文"); + } + + // 设置为当前上下文并初始化 + GLFW.glfwMakeContextCurrent(windowId); + GL.createCapabilities(); + + // 初始化 OpenGL 状态 + GL11.glEnable(GL11.GL_DEPTH_TEST); + + // 检查是否支持多重采样 + if (GL.getCapabilities().OpenGL13) { + GL11.glEnable(GL13.GL_MULTISAMPLE); + System.out.println("多重采样已启用"); + } else { + System.out.println("不支持多重采样,跳过启用"); + } + + GL11.glViewport(0, 0, width, height); + + ModelRender.initialize(); + + contextInitialized = true; + + // 在正确的上下文中加载模型 + loadModelInContext(); + + // 通知上下文已准备就绪 + contextReady.complete(null); + } + + /** + * 在 OpenGL 上下文中加载模型 + */ + private void loadModelInContext() { + try { + if (modelPath != null) { + Model2D model = Model2D.loadFromFile(modelPath); + modelRef.set(model); + System.out.println("模型加载成功: " + modelPath); + } + } catch (Exception e) { + System.err.println("模型加载失败: " + e.getMessage()); + e.printStackTrace(); + + // 创建错误模型或使用默认模型 + createErrorModel(); + } + } + + /** + * 创建错误模型作为回退 + */ + private void createErrorModel() { + try { + // 这里可以创建一个简单的默认模型 + // 或者保持 modelRef 为 null,在渲染时显示错误信息 + System.out.println("使用默认错误模型"); + } catch (Exception e) { + System.err.println("创建错误模型失败: " + e.getMessage()); + } + } + + /** + * 启动渲染线程 + */ + private void startRendering() { + renderThread = new Thread(() -> { + try { + createOffscreenContext(); + + // 等待上下文就绪后再开始渲染循环 + contextReady.get(); + + // 高性能渲染循环 + while (running && !GLFW.glfwWindowShouldClose(windowId)) { + renderFrame(); + + // 控制帧率 + try { + Thread.sleep(1000 / 60); // 60 FPS + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + cleanup(); + } + }); + + renderThread.setDaemon(true); + renderThread.setName("GL-Render-Thread"); + renderThread.start(); + } + + /** + * 渲染单帧并读取到 BufferedImage + */ + private void renderFrame() { + if (!contextInitialized || windowId == 0) return; + + // 确保在当前上下文中 + GLFW.glfwMakeContextCurrent(windowId); + + Model2D currentModel = modelRef.get(); + if (currentModel != null) { + try { + // 清除缓冲区 + GL11.glClearColor(0.18f, 0.18f, 0.25f, 1f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + + // 渲染模型 + ModelRender.render(1.0f / 60f, currentModel); + + // 读取像素数据到 BufferedImage + readPixelsToImage(); + } catch (Exception e) { + System.err.println("渲染错误: " + e.getMessage()); + renderErrorFrame(e.getMessage()); + } + } else { + // 没有模型时显示默认背景 + renderDefaultBackground(); + } + + // 在 Swing EDT 中更新显示 + SwingUtilities.invokeLater(this::repaint); + } + + /** + * 渲染错误帧 + */ + private void renderErrorFrame(String errorMessage) { + GL11.glClearColor(0.3f, 0.1f, 0.1f, 1f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + readPixelsToImage(); + + // 创建错误图像 + BufferedImage errorImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = errorImage.createGraphics(); + g2d.setColor(Color.DARK_GRAY); + g2d.fillRect(0, 0, width, height); + g2d.setColor(Color.RED); + g2d.drawString("渲染错误: " + errorMessage, 10, 20); + g2d.dispose(); + currentFrame = errorImage; + } + + /** + * 渲染默认背景 + */ + private void renderDefaultBackground() { + GL11.glClearColor(0.1f, 0.1f, 0.15f, 1f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + readPixelsToImage(); + } + + /** + * 读取 OpenGL 像素数据到 BufferedImage + */ + 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); + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + // 转换像素数据 + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int i = (x + (height - y - 1) * width) * 4; // 翻转 Y 轴 + + int r = buffer.get(i) & 0xFF; + int g = buffer.get(i + 1) & 0xFF; + int b = buffer.get(i + 2) & 0xFF; + + int rgb = (r << 16) | (g << 8) | b; + image.setRGB(x, y, rgb); + } + } + + currentFrame = image; + } catch (Exception e) { + System.err.println("读取像素数据错误: " + e.getMessage()); + // 创建错误图像 + BufferedImage errorImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = errorImage.createGraphics(); + g2d.setColor(Color.BLACK); + g2d.fillRect(0, 0, width, height); + g2d.setColor(Color.RED); + g2d.drawString("像素读取失败", 10, 20); + g2d.dispose(); + currentFrame = errorImage; + } + } + + @Override + 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); + } + + // 如果模型为空,显示提示 + if (modelRef.get() == null) { + g.setColor(Color.YELLOW); + g.drawString("模型未加载", 10, 20); + } + } + + /** + * 设置模型(线程安全) + */ + public void setModel(Model2D model) { + // 等待上下文就绪后再设置模型 + contextReady.thenRun(() -> { + modelRef.set(model); + System.out.println("模型已更新"); + }); + } + + /** + * 获取当前渲染的模型 + */ + public Model2D getModel() { + return modelRef.get(); + } + + /** + * 重新设置面板大小 + */ + public void resize(int newWidth, int newHeight) { + setPreferredSize(new Dimension(newWidth, newHeight)); + revalidate(); + + // 在渲染线程中更新视口 + contextReady.thenRun(() -> { + if (contextInitialized && windowId != 0) { + GLFW.glfwMakeContextCurrent(windowId); + GL11.glViewport(0, 0, newWidth, newHeight); + + // 重新创建帧缓冲图像 + currentFrame = null; + } + }); + } + + /** + * 等待渲染上下文准备就绪 + */ + public CompletableFuture waitForContext() { + return contextReady; + } + + /** + * 检查是否正在运行 + */ + public boolean isRunning() { + return running && contextInitialized; + } + + /** + * 清理资源 + */ + public void dispose() { + running = false; + if (renderThread != null) { + try { + renderThread.join(2000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + cleanup(); + } + + private void cleanup() { + if (windowId != 0) { + GLFW.glfwDestroyWindow(windowId); + windowId = 0; + } + GLFW.glfwTerminate(); + System.out.println("OpenGL 资源已清理"); + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java index dd90b79..e40b45f 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java @@ -792,7 +792,7 @@ public final class ModelRender { private static void checkGLError(String op) { int e = GL11.glGetError(); if (e != GL11.GL_NO_ERROR) { - logger.error("OpenGL error during {}: {}", op, getGLErrorString(e)); + //logger.error("OpenGL error during {}: {}", op, getGLErrorString(e)); } } 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 bfc9b82..8eff6ec 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,6 @@ package com.chuangzhou.vivid2D.render.model; +import javax.swing.tree.DefaultMutableTreeNode; import com.chuangzhou.vivid2D.render.model.data.ModelData; import com.chuangzhou.vivid2D.render.model.data.ModelMetadata; import com.chuangzhou.vivid2D.render.model.util.*; @@ -350,6 +351,14 @@ public class Model2D { } } + public DefaultMutableTreeNode toTreeNode() { + DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(this.name != null ? this.name : "模型"); + for (ModelPart part : parts) { + rootNode.add(part.toTreeNode()); + } + return rootNode; + } + /** * 捕获当前模型状态到姿态 */ diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java index b34035a..6427c65 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import javax.swing.tree.DefaultMutableTreeNode; /** * 2D模型部件,支持层级变换和变形器 @@ -133,6 +134,14 @@ public class ModelPart { return new ArrayList<>(children); } + public DefaultMutableTreeNode toTreeNode() { + DefaultMutableTreeNode node = new DefaultMutableTreeNode(this.name != null ? this.name : "部件"); + for (ModelPart child : children) { + node.add(child.toTreeNode()); + } + return node; + } + /** * 根据名称查找子部件 */ diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java index 48beeb8..2822963 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java @@ -1,6 +1,5 @@ package com.chuangzhou.vivid2D.render.model.util; -import com.chuangzhou.vivid2D.render.model.ModelPart; import org.joml.Vector2f; import java.nio.FloatBuffer; @@ -503,7 +502,7 @@ public class Mesh2D { org.lwjgl.system.MemoryUtil.memFree(fb); } } else { - logger.warn("警告: 着色器中未找到 uModelMatrix 或 uModel uniform"); + //logger.warn("警告: 着色器中未找到 uModelMatrix 或 uModel uniform"); } // 绘制 diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/PhysicsSystem.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/PhysicsSystem.java index f300d64..0d0f83c 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/PhysicsSystem.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/PhysicsSystem.java @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap; * - 在碰撞处理里同时更新位置与速度(冲量响应),并避免在碰撞后覆盖冲量 * - 约束迭代次数可调,布料等需要多个迭代(默认 3) * - * @author tzdwindows 7 (modified) + * @author tzdwindows 7 */ public class PhysicsSystem { // ==================== 物理参数 ==================== diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Texture.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Texture.java index b6d160c..33a59bb 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Texture.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Texture.java @@ -6,14 +6,12 @@ import org.lwjgl.opengl.GL12; 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; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/chuangzhou/vivid2D/test/TestModelGLPanel.java b/src/main/java/com/chuangzhou/vivid2D/test/TestModelGLPanel.java new file mode 100644 index 0000000..1d8e256 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/test/TestModelGLPanel.java @@ -0,0 +1,29 @@ +package com.chuangzhou.vivid2D.test; + +import com.chuangzhou.vivid2D.render.ModelGLPanel; +import com.chuangzhou.vivid2D.render.model.Model2D; + +import javax.swing.*; + +public class TestModelGLPanel { + + private static final String MODEL_PATH = "C:\\Users\\Administrator\\Desktop\\trump_texture.model"; + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("ModelGLPanel Demo"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + //com.chuangzhou.vivid2D.render.model.Model2D model = com.chuangzhou.vivid2D.render.model.Model2D.loadFromFile(MODEL_PATH); + ModelGLPanel glPanel = null; + try { + Model2D model2D = new Model2D("Hi"); + glPanel = new ModelGLPanel(MODEL_PATH, 800, 600); + } catch (Exception e) { + throw new RuntimeException(e); + } + frame.add(glPanel); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + } +} diff --git a/src/main/resources/vivid2D/Vivid2D.png b/src/main/resources/vivid2D/Vivid2D.png new file mode 100644 index 0000000..a8dcfaf Binary files /dev/null and b/src/main/resources/vivid2D/Vivid2D.png differ