From 1c75006d514de892a6cb01e27299fad705fd4c1b Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Thu, 6 Nov 2025 16:51:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(render):=20=E5=AE=9E=E7=8E=B0=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=BC=82=E6=AD=A5=E5=8A=A0=E8=BD=BD=E4=B8=8E=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 GLContextManager 中添加模型加载完成的 CompletableFuture 支持- 优化 LiquifyTargetPartRander 使用 renderVertices 替代 vertices- 移除 LiquifyTool 中冗余的 Ctrl 键判断与强制重绘逻辑 - Mesh2D 中移除已废弃的 pinnedController 字段 - MeshData 中新增 renderVertices、isSuspension 等渲染相关字段- ModelLayerPanel 支持模型异步加载完成后的初始化 - ModelRenderPanel 添加模型获取的同步等待机制 - 清理大量冗余注释与无用代码,提升代码可读性 --- .../vivid2D/render/ModelRender.java | 5 - .../vivid2D/render/awt/ModelLayerPanel.java | 85 +++------- .../vivid2D/render/awt/ModelRenderPanel.java | 26 +-- .../render/awt/manager/GLContextManager.java | 33 +++- .../vivid2D/render/awt/tools/LiquifyTool.java | 7 - .../vivid2D/render/model/ModelPart.java | 24 +-- .../vivid2D/render/model/data/MeshData.java | 20 +++ .../vivid2D/render/model/util/Mesh2D.java | 12 +- .../util/tools/LiquifyTargetPartRander.java | 14 +- .../vivid2D/test/ModelLayerPanelTest.java | 160 ++++++------------ 10 files changed, 163 insertions(+), 223 deletions(-) diff --git a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java index 61654bd..2a9462a 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java @@ -461,14 +461,9 @@ public final class ModelRender { } private static void logGLInfo() { - logger.info("OpenGL Vendor: {}", RenderSystem.getVendor()); - logger.info("OpenGL Renderer: {}", RenderSystem.getRenderer()); - logger.info("OpenGL Version: {}", RenderSystem.getOpenGLVersion()); - logger.info("GLSL Version: {}", RenderSystem.getGLSLVersion()); RenderSystem.logDetailedGLInfo(); } - private static void uploadLightsToShader(ShaderProgram sp, Model2D model) { List lights = model.getLights(); int idx = 0; diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelLayerPanel.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelLayerPanel.java index 4ed40aa..7172d42 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelLayerPanel.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelLayerPanel.java @@ -1,4 +1,3 @@ -// ModelLayerPanel.java (现代化重构) package com.chuangzhou.vivid2D.render.awt; import com.chuangzhou.vivid2D.render.awt.manager.LayerOperationManager; @@ -36,7 +35,6 @@ public class ModelLayerPanel extends JPanel { private DefaultListModel listModel; private JList layerList; - // 现代化UI组件 private ModernButton addButton; private ModernButton removeButton; private ModernButton upButton; @@ -51,37 +49,40 @@ public class ModelLayerPanel extends JPanel { private volatile boolean ignoreSliderEvents = false; - // 使用重构后的工具类 private ThumbnailManager thumbnailManager; private PSDImporter psdImporter; private LayerOperationManager operationManager; - // 现代化颜色方案 private static final Color BACKGROUND_COLOR = new Color(45, 45, 48); private static final Color SURFACE_COLOR = new Color(62, 62, 66); private static final Color ACCENT_COLOR = new Color(0, 122, 204); private static final Color TEXT_COLOR = new Color(241, 241, 241); private static final Color BORDER_COLOR = new Color(87, 87, 87); - public ModelLayerPanel(Model2D model) { - this(model, null); - } - - public ModelLayerPanel(Model2D model, ModelRenderPanel renderPanel) { - this.model = model; + public ModelLayerPanel(ModelRenderPanel renderPanel) { this.renderPanel = renderPanel; - - // 设置现代化外观 + this.model = renderPanel.getModel(); setupModernLookAndFeel(); - - // 初始化工具类 this.thumbnailManager = new ThumbnailManager(renderPanel); - this.psdImporter = new PSDImporter(model, renderPanel, this); - this.operationManager = new LayerOperationManager(model); - - initComponents(); - reloadFromModel(); - generateAllThumbnails(); + if (this.model != null) { + this.psdImporter = new PSDImporter(model, renderPanel, this); + this.operationManager = new LayerOperationManager(model); + initComponents(); + reloadFromModel(); + generateAllThumbnails(); + } else { + initComponents(); + renderPanel.getGlContextManager().waitForModel().thenAccept(m -> { + if (m == null) return; + SwingUtilities.invokeLater(() -> { + this.model = m; + this.psdImporter = new PSDImporter(model, renderPanel, ModelLayerPanel.this); + this.operationManager = new LayerOperationManager(model); + reloadFromModel(); + generateAllThumbnails(); + }); + }); + } } private void setupModernLookAndFeel() { @@ -119,13 +120,10 @@ public class ModelLayerPanel extends JPanel { } } - // ============== 现代化组件初始化 ============== private void initComponents() { setLayout(new BorderLayout(10, 10)); listModel = new DefaultListModel<>(); layerList = createModernList(); - - // 创建现代化布局 createHeaderPanel(); createCenterPanel(); createControlPanel(); @@ -137,19 +135,13 @@ public class ModelLayerPanel extends JPanel { list.setBackground(SURFACE_COLOR); list.setForeground(TEXT_COLOR); list.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - list.setFixedCellHeight(70); // 增加行高以显示缩略图 - - // 使用独立的渲染器 + list.setFixedCellHeight(70); LayerCellRenderer cellRenderer = new LayerCellRenderer(this, thumbnailManager); cellRenderer.attachMouseListener(list, listModel); list.setCellRenderer(cellRenderer); - - // 使用独立的拖拽处理器 list.setDragEnabled(true); list.setTransferHandler(new LayerReorderTransferHandler(this)); list.setDropMode(DropMode.INSERT); - - // 双击重命名 list.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -161,10 +153,7 @@ public class ModelLayerPanel extends JPanel { } } }); - - // 选择变更监听器 list.addListSelectionListener(e -> updateUIState()); - return list; } @@ -185,23 +174,14 @@ public class ModelLayerPanel extends JPanel { JScrollPane scrollPane = new JScrollPane(layerList); scrollPane.setBorder(createModernBorder("图层列表")); scrollPane.getViewport().setBackground(SURFACE_COLOR); - - // 自定义滚动条 - JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); - //verticalScrollBar.setUI(new ModernScrollBarUI()); - add(scrollPane, BorderLayout.CENTER); } private void createControlPanel() { JPanel controlPanel = new JPanel(new BorderLayout(10, 10)); controlPanel.setBackground(BACKGROUND_COLOR); - - // 顶部按钮面板 controlPanel.add(createButtonPanel(), BorderLayout.NORTH); - // 底部设置面板 controlPanel.add(createSettingsPanel(), BorderLayout.SOUTH); - add(controlPanel, BorderLayout.SOUTH); } @@ -210,14 +190,12 @@ public class ModelLayerPanel extends JPanel { buttonPanel.setBackground(BACKGROUND_COLOR); buttonPanel.setBorder(createModernBorder("操作")); - // 创建现代化按钮 addButton = createIconButton("⊕", "添加图层", this::showAddMenu); removeButton = createIconButton("⊖", "删除选中图层", this::onRemoveLayer); upButton = createIconButton("↑", "上移图层", this::moveSelectedUp); downButton = createIconButton("↓", "下移图层", this::moveSelectedDown); bindTextureButton = createIconButton("📷", "绑定贴图", this::bindTextureToSelectedPart); - // 初始禁用状态 removeButton.setEnabled(false); upButton.setEnabled(false); downButton.setEnabled(false); @@ -237,7 +215,6 @@ public class ModelLayerPanel extends JPanel { settingsPanel.setBackground(BACKGROUND_COLOR); settingsPanel.setBorder(createModernBorder("图层设置")); - // 不透明度控制 JPanel opacityPanel = new JPanel(new BorderLayout(8, 0)); opacityPanel.setBackground(BACKGROUND_COLOR); @@ -267,9 +244,6 @@ public class ModelLayerPanel extends JPanel { slider.setBackground(BACKGROUND_COLOR); slider.setForeground(ACCENT_COLOR); - // 自定义滑块UI - //slider.setUI(new ModernSliderUI()); - return slider; } @@ -322,7 +296,6 @@ public class ModelLayerPanel extends JPanel { operationManager.addLayer(name); reloadFromModel(); - // 选中新创建的部件 ModelPart newPart = findPartByName(name); if (newPart != null) { selectPart(newPart); @@ -341,7 +314,6 @@ public class ModelLayerPanel extends JPanel { return model.getPartMap(); } - // ============== 现代化对话框方法 ============== private void showRenameDialog(ModelPart part) { String newName = (String) JOptionPane.showInputDialog( this, @@ -360,7 +332,6 @@ public class ModelLayerPanel extends JPanel { } } - // ============== 原有业务方法(保持不变) ============== public void setModel(Model2D model) { this.model = model; this.psdImporter = new PSDImporter(model, renderPanel, this); @@ -454,7 +425,6 @@ public class ModelLayerPanel extends JPanel { } } - // 现代化边框 private TitledBorder createModernBorder(String title) { TitledBorder border = BorderFactory.createTitledBorder( BorderFactory.createLineBorder(BORDER_COLOR, 1, true), @@ -485,7 +455,7 @@ public class ModelLayerPanel extends JPanel { } // 现代化按钮类 - private class ModernButton extends JButton { + private static class ModernButton extends JButton { public ModernButton(String text) { super(text); setupModernStyle(); @@ -520,7 +490,7 @@ public class ModelLayerPanel extends JPanel { } // 现代化菜单项类 - private class ModernMenuItem extends JMenuItem { + private static class ModernMenuItem extends JMenuItem { public ModernMenuItem(String text) { super(text); setBackground(SURFACE_COLOR); @@ -544,16 +514,13 @@ public class ModelLayerPanel extends JPanel { } // 现代化弹出菜单 - private class ModernPopupMenu extends JPopupMenu { + private static class ModernPopupMenu extends JPopupMenu { public ModernPopupMenu() { setBackground(SURFACE_COLOR); setBorder(BorderFactory.createLineBorder(BORDER_COLOR)); } } - // 其余原有方法保持不变... - // (reloadFromModel, performVisualReorder, bindTextureToSelectedPart等) - public void reloadFromModel() { ModelPart selected = layerList.getSelectedValue(); @@ -745,13 +712,11 @@ public class ModelLayerPanel extends JPanel { String name = JOptionPane.showInputDialog(this, "新图层名称:", f.getName()); if (name == null || name.trim().isEmpty()) name = f.getName(); - // 创建部件与 Mesh ModelPart part = model.createPart(name); Mesh2D mesh = MeshTextureUtil.createQuadForImage(img, name + "_mesh"); mesh.createDefaultSecondaryVertices(); part.addMesh(mesh); - // 创建纹理 if (renderPanel != null) { final String texName = name + "_tex"; final String filePath = f.getAbsolutePath(); diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java index 3a7f177..dcddf49 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java @@ -27,6 +27,7 @@ import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; /** @@ -100,6 +101,8 @@ public class ModelRenderPanel extends JPanel { handleSingleClick(); }); doubleClickTimer.setRepeats(false); + + modelsUpdate(getModel()); } /** @@ -541,7 +544,7 @@ public class ModelRenderPanel extends JPanel { g2d.setColor(Color.DARK_GRAY); g2d.fillRect(0, 0, panelW, panelH); } - if (modelRef.get() == null) { + if (getModel() == null) { g2d.setColor(new Color(255, 255, 0, 200)); g2d.drawString("模型未加载", 10, 20); } @@ -550,20 +553,23 @@ public class ModelRenderPanel extends JPanel { } } - /** - * 设置模型 - */ - public void setModel(Model2D model) { - glContextManager.executeInGLContext(() -> { - modelRef.set(model); - logger.info("模型已更新"); - }); + public void modelsUpdate(Model2D model){ + for (int i = 0; i < model.getParts().size(); i++) { + model.getParts().get(i).setPosition(model.getParts().get(i).getPosition().x, model.getParts().get(i).getPosition().y); + } } /** * 获取当前渲染的模型 */ public Model2D getModel() { + if (modelRef.get() == null) { + try { + return glContextManager.waitForModel().get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } return modelRef.get(); } @@ -621,7 +627,7 @@ public class ModelRenderPanel extends JPanel { * 通过网格查找对应的 ModelPart */ public ModelPart findPartByMesh(Mesh2D mesh) { - Model2D model = modelRef.get(); + Model2D model = getModel(); if (model == null) return null; for (ModelPart part : model.getParts()) { ModelPart found = findPartByMeshRecursive(part, mesh); diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/GLContextManager.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/GLContextManager.java index 98443ff..667797b 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/GLContextManager.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/GLContextManager.java @@ -49,6 +49,8 @@ public class GLContextManager { private static final float ZOOM_SMOOTHING = 0.18f; // 0..1, 越大收敛越快(建议 0.12-0.25) private RepaintCallback repaintCallback; + private final CompletableFuture modelReady = new CompletableFuture<>(); + public GLContextManager(String modelPath, int width, int height) { this.modelPath = modelPath; this.width = width; @@ -60,6 +62,9 @@ public class GLContextManager { this.width = width; this.height = height; this.modelRef.set(model); + if (model != null && !modelReady.isDone()) { + modelReady.complete(model); + } } public int getHeight() { @@ -134,9 +139,17 @@ public class GLContextManager { private void loadModelInContext() { try { if (modelPath != null) { - Model2D model = Model2D.loadFromFile(modelPath); + Model2D model; + try { + model = Model2D.loadFromFile(modelPath); + } catch (Throwable e) { + model = new Model2D("新的项目"); + } modelRef.set(model); logger.info("模型加载成功: {}", modelPath); + if (model != null && !modelReady.isDone()) { + modelReady.complete(model); + } } } catch (Exception e) { logger.error("模型加载失败: {}", e.getMessage(), e); @@ -154,6 +167,10 @@ public class GLContextManager { } renderThread = new Thread(() -> { try { + if (modelRef.get() != null && !modelReady.isDone()) { + modelReady.complete(modelRef.get()); + } + createOffscreenContext(); // 等待上下文就绪后再开始渲染循环(contextReady 由 createOffscreenContext 完成) @@ -590,4 +607,18 @@ public class GLContextManager { public void setCameraDragging(boolean cameraDragging) { this.cameraDragging = cameraDragging; } + + /** + * 从 GLContextManager 获取当前模型引用 + */ + public Model2D getModel() { + return modelRef.get(); + } + + /** + * 等待模型加载完成(若已经完成会立即返回已完成的 CompletableFuture) + */ + public CompletableFuture waitForModel() { + return modelReady; + } } diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java index 4dc3b07..fe2340c 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java @@ -227,16 +227,10 @@ public class LiquifyTool extends Tool { */ private void applyLiquifyEffect(float modelX, float modelY) { if (liquifyTargetPart == null) return; - Vector2f brushCenter = new Vector2f(modelX, modelY); - - // 判断是否按住Ctrl键,决定是否创建顶点 boolean createVertices = renderPanel.getKeyboardManager().getIsCtrlPressed(); - liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize, liquifyBrushStrength, currentLiquifyMode, 1, createVertices); - - // 强制重绘 renderPanel.repaint(); } @@ -246,7 +240,6 @@ public class LiquifyTool extends Tool { private boolean isOverTargetMesh(float modelX, float modelY) { if (liquifyTargetMesh == null) return false; - // 更新边界框 liquifyTargetMesh.updateBounds(); return liquifyTargetMesh.containsPoint(modelX, modelY); } 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 52084e5..4f5483c 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java @@ -2055,40 +2055,24 @@ public class ModelPart { // 1. 保存局部顶点到 originalVertices float[] localVertices = mesh.getVertices().clone(); mesh.setOriginalVertices(localVertices); - - // 2. 确保 renderVertices 数组已初始化 - // (您需要 Mesh2D.java 中有这个方法) - // mesh.ensureRenderVerticesInitialized(); - - // 3. 计算世界坐标并写入 *renderVertices*,而不是 + // 2. 计算世界坐标并写入 *renderVertices*,而不是 int vc = mesh.getVertexCount(); for (int i = 0; i < vc; i++) { Vector2f local = new Vector2f(localVertices[i * 2], localVertices[i * 2 + 1]); Vector2f worldPt = Matrix3fUtils.transformPoint(this.worldTransform, local); - - // 错误:mesh.setVertex(i, worldPt.x, worldPt.y); - // 正确: - mesh.setRenderVertex(i, worldPt.x, worldPt.y); // 假设 setRenderVertex 存在 + // mesh.setVertex(i, worldPt.x, worldPt.y); + mesh.setRenderVertex(i, worldPt.x, worldPt.y); } - - // 4. 同步 pivot + // 3. 同步 pivot try { Vector2f origPivot = mesh.getOriginalPivot(); Vector2f worldPivot = Matrix3fUtils.transformPoint(this.worldTransform, origPivot); mesh.setPivot(worldPivot.x, worldPivot.y); // 现在这个会成功(因为步骤1的修复) } catch (Exception ignored) { } - - // ==================== 新增:初始化木偶控制点的位置 ==================== initializePuppetPinsPosition(mesh); - - // 标记为已烘焙到世界坐标(语义上明确),并确保 bounds/dirty 状态被正确刷新 mesh.setBakedToWorld(true); - - // 确保 GPU 数据在下一次绘制时会被上传(如果当前在渲染线程,也可以直接 uploadToGPU) mesh.markDirty(); - - // 将拷贝加入到本部件 meshes.add(mesh); boundsDirty = true; } 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 index ea6a339..a328cc0 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java @@ -30,6 +30,10 @@ public class MeshData implements Serializable { // 原始顶点数据(用于变形恢复) public float[] originalVertices; + public float[] renderVertices = null; + public String modelPartName = null; // 关联的ModelPart名称 + public boolean isSuspension = false; + public Vector2f previewPoint = null; // 变换相关 public Vector2f pivot; @@ -83,6 +87,12 @@ public class MeshData implements Serializable { // 保存原始顶点数据 this.originalVertices = mesh.getOriginalVertices(); + this.renderVertices = mesh.getRenderVertices(); // 获取渲染顶点副本 + this.isSuspension = mesh.isSuspension(); + this.previewPoint = mesh.getPreviewPoint() != null ? new Vector2f(mesh.getPreviewPoint()) : null; + if (mesh.getModelPart() != null) { + this.modelPartName = mesh.getModelPart().getName(); + } // 保存变换数据 this.pivot = new Vector2f(mesh.getPivot()); @@ -169,6 +179,11 @@ public class MeshData implements Serializable { // 恢复二级顶点 restoreSecondaryVertices(mesh); + mesh.setSuspension(this.isSuspension); + if (this.previewPoint != null) { + mesh.setPreviewPoint(new Vector2f(this.previewPoint)); + } + // 恢复木偶控制点 restorePuppetPins(mesh); @@ -237,6 +252,11 @@ public class MeshData implements Serializable { copy.bakedToWorld = this.bakedToWorld; copy.isRenderVertices = this.isRenderVertices; + copy.renderVertices = this.renderVertices != null ? this.renderVertices.clone() : null; + copy.modelPartName = this.modelPartName; + copy.isSuspension = this.isSuspension; + copy.previewPoint = this.previewPoint != null ? new Vector2f(this.previewPoint) : null; + // 复制原始顶点数据 copy.originalVertices = this.originalVertices != null ? this.originalVertices.clone() : null; 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 4122f2a..ad7927b 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 @@ -102,7 +102,6 @@ public class Mesh2D { // ==================== 构造器 ==================== private static final float SNAP_THRESHOLD = 0.01f; // 靠近判定阈值(按需要调大,比如 0.1f) - private SecondaryVertex pinnedController = null; // 当前作为“钉子”的控制点(若有) public Mesh2D() { this.name = "unnamed"; this.vertices = new float[0]; @@ -1011,7 +1010,6 @@ public class Mesh2D { v.setPosition(otherPos); // 把被靠近的那个当作钉子 other.setPinned(true); - pinnedController = other; // 把移动到其上的顶点锁定 v.setLocked(true); logger.info("SecondaryVertex {} snapped to {}. {} pinned, {} locked.", v.getId(), other.getId(), other.getId(), v.getId()); @@ -1048,7 +1046,6 @@ public class Mesh2D { sv.setPinned(false); sv.setLocked(false); } - pinnedController = null; logger.info("All secondary vertices unpinned/unlocked"); } @@ -2291,6 +2288,10 @@ public class Mesh2D { return puppetPins.size(); } + public boolean isSuspension() { + return isSuspension; + } + /** * 顶点变换器接口 */ @@ -2684,15 +2685,10 @@ public class Mesh2D { * 标记数据已修改 */ public void markDirty() { - // 删除旧 GPU 对象(若有),并标记脏 deleteGPU(); this.dirty = true; this.boundsDirty = true; this.multiSelectionDirty = true; - - // 渲染缓存(renderVertices)不再可信,清除烘焙标志 - this.bakedToWorld = false; - // 注意:不立即清除 renderVertices 数组(保留以供 debug),但 bakedToWorld=false 可确保上传使用局部 vertices } diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java index 4d14cc7..386c31c 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java @@ -98,7 +98,7 @@ public class LiquifyTargetPartRander extends RanderTools { drawRoundedBoundingOutline(bb, bounds, outline, 1.0f, 0f); // 优化点:根据顶点数量选择低/高质量绘制 - int vertexCount = mesh2D.getVertices() == null ? 0 : mesh2D.getVertices().length / 2; + int vertexCount = mesh2D.getRenderVertices() == null ? 0 : mesh2D.getRenderVertices().length / 2; boolean large = vertexCount > LARGE_VERTEX_THRESHOLD; // 边框:始终绘制(合并一次 begin/end) @@ -172,13 +172,13 @@ public class LiquifyTargetPartRander extends RanderTools { // 合并绘制外轮廓(单次 begin/end) private void drawOutlineOnce(BufferBuilder bb, Mesh2D mesh2D) { - if (mesh2D.getVertices() == null || mesh2D.getVertices().length < 4) return; + if (mesh2D.getRenderVertices() == null || mesh2D.getRenderVertices().length < 4) return; Vector4f OUTER_LINE = new Vector4f(1f, 0.85f, 0.35f, 0.12f); - int count = mesh2D.getVertices().length / 2; + int count = mesh2D.getRenderVertices().length / 2; GL11.glLineWidth(1.0f); bb.begin(GL11.GL_LINE_LOOP, count); bb.setColor(OUTER_LINE); - float[] verts = mesh2D.getVertices(); + float[] verts = mesh2D.getRenderVertices(); for (int i = 0; i < count; i++) { int base = i * 2; bb.vertex(verts[base], verts[base + 1], 0f, 0f); @@ -195,7 +195,7 @@ public class LiquifyTargetPartRander extends RanderTools { int lines = (idx.length / 3) * 6; // 每三角 6 顶点(3 条线) bb.begin(GL11.GL_LINES, lines); bb.setColor(innerLine); - float[] verts = mesh2D.getVertices(); + float[] verts = mesh2D.getRenderVertices(); for (int i = 0; i < idx.length; i += 3) { int i1 = idx[i]; int i2 = idx[i + 1]; @@ -215,13 +215,13 @@ public class LiquifyTargetPartRander extends RanderTools { // 顶点点标记 - 优化版:大网格时采样绘制、减少 segment、合并操作 private void drawLiquifyVertexPointsOptimized(Mesh2D mesh2D, BufferBuilder bb, List pendingTexts, boolean large, int vertexCount) { - if (mesh2D.getVertices() == null) return; + if (mesh2D.getRenderVertices() == null) return; final float BASE_SIZE = Math.max(1.5f, mesh2D.secondaryVertexSize * (large ? 0.35f : 0.6f)); final Vector4f FILL = new Vector4f(1f, 0.6f, 0.15f, large ? 0.10f : 0.18f); final Vector4f STROKE = new Vector4f(1f, 1f, 1f, large ? 0.75f : 0.9f); - float[] verts = mesh2D.getVertices(); + float[] verts = mesh2D.getRenderVertices(); // 采样步长,保证绘制点数量不会超过 TARGET_VERTEX_DRAW(越大网格步长越大) int step = 1; diff --git a/src/main/java/com/chuangzhou/vivid2D/test/ModelLayerPanelTest.java b/src/main/java/com/chuangzhou/vivid2D/test/ModelLayerPanelTest.java index 887adb8..422537d 100644 --- a/src/main/java/com/chuangzhou/vivid2D/test/ModelLayerPanelTest.java +++ b/src/main/java/com/chuangzhou/vivid2D/test/ModelLayerPanelTest.java @@ -7,6 +7,7 @@ import com.chuangzhou.vivid2D.render.model.Model2D; import com.chuangzhou.vivid2D.render.model.ModelPart; import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; @@ -22,7 +23,6 @@ import java.util.List; public class ModelLayerPanelTest { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { - //LookAndFeel defaultLaf = isDarkMode ? : new FlatMacLightLaf(); try { UIManager.setLookAndFeel(new FlatMacDarkLaf()); } catch (UnsupportedLookAndFeelException e) { @@ -30,129 +30,79 @@ public class ModelLayerPanelTest { } System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8)); System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8)); - // 创建示例模型并添加图层 - Model2D model = new Model2D("示例模型"); - - // 调整一些初始属性(可选) - ModelPart person = model.getPart("人物"); - if (person != null) { - try { - person.setOpacity(0.85f); - } catch (Exception ignored) { - } - } - - // 创建 UI JFrame frame = new JFrame("ModelLayerPanel 测试(含渲染面板和变换面板)"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setLayout(new BorderLayout()); - - // 左侧:图层面板(传入 renderPanel 后可在面板中绑定贴图到 GL 上下文) - // 先创建一个占位 renderPanel,再把它传给 layerPanel(ModelRenderPanel 构造需要尺寸) - ModelRenderPanel renderPanel = new ModelRenderPanel(model, 640, 480); - ModelLayerPanel layerPanel = new ModelLayerPanel(model, renderPanel); + ModelRenderPanel renderPanel = new ModelRenderPanel("C:\\Users\\Administrator\\Desktop\\testing.model", 640, 480); + ModelLayerPanel layerPanel = new ModelLayerPanel(renderPanel); layerPanel.setPreferredSize(new Dimension(260, 600)); frame.add(layerPanel, BorderLayout.WEST); - - // 中间:渲染面板 renderPanel.setPreferredSize(new Dimension(640, 480)); frame.add(renderPanel, BorderLayout.CENTER); - - // 创建变换面板 TransformPanel transformPanel = new TransformPanel(renderPanel); - - // 右侧:创建选项卡面板,包含模型树和变换面板 JTabbedPane rightTabbedPane = new JTabbedPane(); - - // 模型树选项卡 - JTree tree = new JTree(model.toTreeNode()); - JScrollPane treeScroll = new JScrollPane(tree); - treeScroll.setPreferredSize(new Dimension(280, 600)); - rightTabbedPane.addTab("模型结构", treeScroll); - - // 变换面板选项卡 - JScrollPane transformScroll = new JScrollPane(transformPanel); - transformScroll.setPreferredSize(new Dimension(280, 600)); - rightTabbedPane.addTab("变换控制", transformScroll); - - rightTabbedPane.setPreferredSize(new Dimension(300, 600)); - frame.add(rightTabbedPane, BorderLayout.EAST); - - // 底部:演示按钮(刷新树以反映面板中对模型的更改) - JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT)); - JButton refreshBtn = new JButton("刷新模型树"); - refreshBtn.addActionListener(e -> { - tree.setModel(new javax.swing.tree.DefaultTreeModel(model.toTreeNode())); - for (int i = 0; i < tree.getRowCount(); i++) tree.expandRow(i); - // 同步通知渲染面板(如果需要)去刷新模型 - try { - renderPanel.setModel(model); - } catch (Exception ignored) { - } + renderPanel.getGlContextManager().waitForModel().thenAccept(model -> { + if (model == null) return; + JTree tree = new JTree(model.toTreeNode()); + JScrollPane treeScroll = new JScrollPane(tree); + treeScroll.setPreferredSize(new Dimension(280, 600)); + rightTabbedPane.addTab("模型结构", treeScroll); + JScrollPane transformScroll = new JScrollPane(transformPanel); + transformScroll.setPreferredSize(new Dimension(280, 600)); + rightTabbedPane.addTab("变换控制", transformScroll); + rightTabbedPane.setPreferredSize(new Dimension(300, 600)); + frame.add(rightTabbedPane, BorderLayout.EAST); + JPanel bottom = getBottom(renderPanel, transformPanel); + frame.add(bottom, BorderLayout.SOUTH); + renderPanel.addModelClickListener((mesh, modelX, modelY, screenX, screenY) -> { + if (mesh == null) return; + System.out.println("点击了模型:" + mesh.getName() + ",模型坐标:" + modelX + ", " + modelY + ",屏幕坐标:" + screenX + ", " + screenY); + List selectedPart = renderPanel.getSelectedParts(); + transformPanel.setSelectedParts(selectedPart); + rightTabbedPane.setSelectedIndex(1); + }); + frame.addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosed(java.awt.event.WindowEvent e) { + try { + renderPanel.getGlContextManager().dispose(); + } catch (Throwable ignored) { + } + model.saveToFile("C:\\Users\\Administrator\\Desktop\\testing.model"); + System.exit(0); + } + }); + frame.setSize(1300, 700); + frame.setLocationRelativeTo(null); + frame.setVisible(true); }); - bottom.add(refreshBtn); + }); + } - JButton printOrderBtn = new JButton("打印部件顺序(控制台)"); - printOrderBtn.addActionListener(e -> { - System.out.println("当前模型部件顺序:"); + private static @NotNull JPanel getBottom(ModelRenderPanel renderPanel, TransformPanel transformPanel) { + JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT)); + + JButton printOrderBtn = new JButton("打印部件顺序(控制台)"); + printOrderBtn.addActionListener(e -> { + System.out.println("当前模型部件顺序:"); + renderPanel.getGlContextManager().waitForModel().thenAccept(model -> { + if (model == null) return; for (ModelPart p : model.getParts()) { System.out.println(" - " + p.getName() + " (可见=" + p.isVisible() + ", 不透明度=" + p.getOpacity() + ")"); } }); - bottom.add(printOrderBtn); + }); + bottom.add(printOrderBtn); - // 添加选中部件更新按钮 - JButton updateSelectionBtn = new JButton("更新选中部件"); - updateSelectionBtn.addActionListener(e -> { - renderPanel.getGlContextManager().executeInGLContext(() -> { - List selectedPart = renderPanel.getSelectedParts(); - transformPanel.setSelectedParts(selectedPart); - }); - }); - bottom.add(updateSelectionBtn); - - frame.add(bottom, BorderLayout.SOUTH); - - // 添加模型点击监听器,自动更新变换面板的选中部件 - renderPanel.addModelClickListener((mesh, modelX, modelY, screenX, screenY) -> { - if (mesh == null) return; - System.out.println("点击了模型:" + mesh.getName() + ",模型坐标:" + modelX + ", " + modelY + ",屏幕坐标:" + screenX + ", " + screenY); - - // 自动更新变换面板的选中部件 + // 添加选中部件更新按钮 + JButton updateSelectionBtn = new JButton("更新选中部件"); + updateSelectionBtn.addActionListener(e -> { + renderPanel.getGlContextManager().executeInGLContext(() -> { List selectedPart = renderPanel.getSelectedParts(); transformPanel.setSelectedParts(selectedPart); - - // 切换到变换控制选项卡 - rightTabbedPane.setSelectedIndex(1); }); - - // 监听窗口关闭,确保释放 GL 资源 - frame.addWindowListener(new java.awt.event.WindowAdapter() { - @Override - public void windowClosing(java.awt.event.WindowEvent e) { - // 先释放渲染面板相关 GL 资源与线程 - //try { - // renderPanel.dispose(); - //} catch (Throwable t) { - // t.printStackTrace(); - //} - } - - @Override - public void windowClosed(java.awt.event.WindowEvent e) { - // 进程退出(确保彻底关闭) - try { - renderPanel.getGlContextManager().dispose(); - } catch (Throwable ignored) { - } - model.saveToFile("C:\\Users\\Administrator\\Desktop\\testing.model"); - System.exit(0); - } - }); - - frame.setSize(1300, 700); - frame.setLocationRelativeTo(null); - frame.setVisible(true); }); + bottom.add(updateSelectionBtn); + return bottom; } } \ No newline at end of file