feat(render): 实现模型异步加载与渲染优化

- 在 GLContextManager 中添加模型加载完成的 CompletableFuture 支持- 优化 LiquifyTargetPartRander 使用 renderVertices 替代 vertices- 移除 LiquifyTool 中冗余的 Ctrl 键判断与强制重绘逻辑
- Mesh2D 中移除已废弃的 pinnedController 字段
- MeshData 中新增 renderVertices、isSuspension 等渲染相关字段- ModelLayerPanel 支持模型异步加载完成后的初始化
- ModelRenderPanel 添加模型获取的同步等待机制
- 清理大量冗余注释与无用代码,提升代码可读性
This commit is contained in:
tzdwindows 7
2025-11-06 16:51:29 +08:00
parent 9a8fe43f7b
commit 1c75006d51
10 changed files with 163 additions and 223 deletions

View File

@@ -461,14 +461,9 @@ public final class ModelRender {
} }
private static void logGLInfo() { 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(); RenderSystem.logDetailedGLInfo();
} }
private static void uploadLightsToShader(ShaderProgram sp, Model2D model) { private static void uploadLightsToShader(ShaderProgram sp, Model2D model) {
List<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights(); List<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights();
int idx = 0; int idx = 0;

View File

@@ -1,4 +1,3 @@
// ModelLayerPanel.java (现代化重构)
package com.chuangzhou.vivid2D.render.awt; package com.chuangzhou.vivid2D.render.awt;
import com.chuangzhou.vivid2D.render.awt.manager.LayerOperationManager; import com.chuangzhou.vivid2D.render.awt.manager.LayerOperationManager;
@@ -36,7 +35,6 @@ public class ModelLayerPanel extends JPanel {
private DefaultListModel<ModelPart> listModel; private DefaultListModel<ModelPart> listModel;
private JList<ModelPart> layerList; private JList<ModelPart> layerList;
// 现代化UI组件
private ModernButton addButton; private ModernButton addButton;
private ModernButton removeButton; private ModernButton removeButton;
private ModernButton upButton; private ModernButton upButton;
@@ -51,37 +49,40 @@ public class ModelLayerPanel extends JPanel {
private volatile boolean ignoreSliderEvents = false; private volatile boolean ignoreSliderEvents = false;
// 使用重构后的工具类
private ThumbnailManager thumbnailManager; private ThumbnailManager thumbnailManager;
private PSDImporter psdImporter; private PSDImporter psdImporter;
private LayerOperationManager operationManager; private LayerOperationManager operationManager;
// 现代化颜色方案
private static final Color BACKGROUND_COLOR = new Color(45, 45, 48); 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 SURFACE_COLOR = new Color(62, 62, 66);
private static final Color ACCENT_COLOR = new Color(0, 122, 204); 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 TEXT_COLOR = new Color(241, 241, 241);
private static final Color BORDER_COLOR = new Color(87, 87, 87); private static final Color BORDER_COLOR = new Color(87, 87, 87);
public ModelLayerPanel(Model2D model) { public ModelLayerPanel(ModelRenderPanel renderPanel) {
this(model, null);
}
public ModelLayerPanel(Model2D model, ModelRenderPanel renderPanel) {
this.model = model;
this.renderPanel = renderPanel; this.renderPanel = renderPanel;
this.model = renderPanel.getModel();
// 设置现代化外观
setupModernLookAndFeel(); setupModernLookAndFeel();
// 初始化工具类
this.thumbnailManager = new ThumbnailManager(renderPanel); this.thumbnailManager = new ThumbnailManager(renderPanel);
this.psdImporter = new PSDImporter(model, renderPanel, this); if (this.model != null) {
this.operationManager = new LayerOperationManager(model); this.psdImporter = new PSDImporter(model, renderPanel, this);
this.operationManager = new LayerOperationManager(model);
initComponents(); initComponents();
reloadFromModel(); reloadFromModel();
generateAllThumbnails(); 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() { private void setupModernLookAndFeel() {
@@ -119,13 +120,10 @@ public class ModelLayerPanel extends JPanel {
} }
} }
// ============== 现代化组件初始化 ==============
private void initComponents() { private void initComponents() {
setLayout(new BorderLayout(10, 10)); setLayout(new BorderLayout(10, 10));
listModel = new DefaultListModel<>(); listModel = new DefaultListModel<>();
layerList = createModernList(); layerList = createModernList();
// 创建现代化布局
createHeaderPanel(); createHeaderPanel();
createCenterPanel(); createCenterPanel();
createControlPanel(); createControlPanel();
@@ -137,19 +135,13 @@ public class ModelLayerPanel extends JPanel {
list.setBackground(SURFACE_COLOR); list.setBackground(SURFACE_COLOR);
list.setForeground(TEXT_COLOR); list.setForeground(TEXT_COLOR);
list.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); list.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
list.setFixedCellHeight(70); // 增加行高以显示缩略图 list.setFixedCellHeight(70);
// 使用独立的渲染器
LayerCellRenderer cellRenderer = new LayerCellRenderer(this, thumbnailManager); LayerCellRenderer cellRenderer = new LayerCellRenderer(this, thumbnailManager);
cellRenderer.attachMouseListener(list, listModel); cellRenderer.attachMouseListener(list, listModel);
list.setCellRenderer(cellRenderer); list.setCellRenderer(cellRenderer);
// 使用独立的拖拽处理器
list.setDragEnabled(true); list.setDragEnabled(true);
list.setTransferHandler(new LayerReorderTransferHandler(this)); list.setTransferHandler(new LayerReorderTransferHandler(this));
list.setDropMode(DropMode.INSERT); list.setDropMode(DropMode.INSERT);
// 双击重命名
list.addMouseListener(new MouseAdapter() { list.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
@@ -161,10 +153,7 @@ public class ModelLayerPanel extends JPanel {
} }
} }
}); });
// 选择变更监听器
list.addListSelectionListener(e -> updateUIState()); list.addListSelectionListener(e -> updateUIState());
return list; return list;
} }
@@ -185,23 +174,14 @@ public class ModelLayerPanel extends JPanel {
JScrollPane scrollPane = new JScrollPane(layerList); JScrollPane scrollPane = new JScrollPane(layerList);
scrollPane.setBorder(createModernBorder("图层列表")); scrollPane.setBorder(createModernBorder("图层列表"));
scrollPane.getViewport().setBackground(SURFACE_COLOR); scrollPane.getViewport().setBackground(SURFACE_COLOR);
// 自定义滚动条
JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar();
//verticalScrollBar.setUI(new ModernScrollBarUI());
add(scrollPane, BorderLayout.CENTER); add(scrollPane, BorderLayout.CENTER);
} }
private void createControlPanel() { private void createControlPanel() {
JPanel controlPanel = new JPanel(new BorderLayout(10, 10)); JPanel controlPanel = new JPanel(new BorderLayout(10, 10));
controlPanel.setBackground(BACKGROUND_COLOR); controlPanel.setBackground(BACKGROUND_COLOR);
// 顶部按钮面板
controlPanel.add(createButtonPanel(), BorderLayout.NORTH); controlPanel.add(createButtonPanel(), BorderLayout.NORTH);
// 底部设置面板
controlPanel.add(createSettingsPanel(), BorderLayout.SOUTH); controlPanel.add(createSettingsPanel(), BorderLayout.SOUTH);
add(controlPanel, BorderLayout.SOUTH); add(controlPanel, BorderLayout.SOUTH);
} }
@@ -210,14 +190,12 @@ public class ModelLayerPanel extends JPanel {
buttonPanel.setBackground(BACKGROUND_COLOR); buttonPanel.setBackground(BACKGROUND_COLOR);
buttonPanel.setBorder(createModernBorder("操作")); buttonPanel.setBorder(createModernBorder("操作"));
// 创建现代化按钮
addButton = createIconButton("", "添加图层", this::showAddMenu); addButton = createIconButton("", "添加图层", this::showAddMenu);
removeButton = createIconButton("", "删除选中图层", this::onRemoveLayer); removeButton = createIconButton("", "删除选中图层", this::onRemoveLayer);
upButton = createIconButton("", "上移图层", this::moveSelectedUp); upButton = createIconButton("", "上移图层", this::moveSelectedUp);
downButton = createIconButton("", "下移图层", this::moveSelectedDown); downButton = createIconButton("", "下移图层", this::moveSelectedDown);
bindTextureButton = createIconButton("📷", "绑定贴图", this::bindTextureToSelectedPart); bindTextureButton = createIconButton("📷", "绑定贴图", this::bindTextureToSelectedPart);
// 初始禁用状态
removeButton.setEnabled(false); removeButton.setEnabled(false);
upButton.setEnabled(false); upButton.setEnabled(false);
downButton.setEnabled(false); downButton.setEnabled(false);
@@ -237,7 +215,6 @@ public class ModelLayerPanel extends JPanel {
settingsPanel.setBackground(BACKGROUND_COLOR); settingsPanel.setBackground(BACKGROUND_COLOR);
settingsPanel.setBorder(createModernBorder("图层设置")); settingsPanel.setBorder(createModernBorder("图层设置"));
// 不透明度控制
JPanel opacityPanel = new JPanel(new BorderLayout(8, 0)); JPanel opacityPanel = new JPanel(new BorderLayout(8, 0));
opacityPanel.setBackground(BACKGROUND_COLOR); opacityPanel.setBackground(BACKGROUND_COLOR);
@@ -267,9 +244,6 @@ public class ModelLayerPanel extends JPanel {
slider.setBackground(BACKGROUND_COLOR); slider.setBackground(BACKGROUND_COLOR);
slider.setForeground(ACCENT_COLOR); slider.setForeground(ACCENT_COLOR);
// 自定义滑块UI
//slider.setUI(new ModernSliderUI());
return slider; return slider;
} }
@@ -322,7 +296,6 @@ public class ModelLayerPanel extends JPanel {
operationManager.addLayer(name); operationManager.addLayer(name);
reloadFromModel(); reloadFromModel();
// 选中新创建的部件
ModelPart newPart = findPartByName(name); ModelPart newPart = findPartByName(name);
if (newPart != null) { if (newPart != null) {
selectPart(newPart); selectPart(newPart);
@@ -341,7 +314,6 @@ public class ModelLayerPanel extends JPanel {
return model.getPartMap(); return model.getPartMap();
} }
// ============== 现代化对话框方法 ==============
private void showRenameDialog(ModelPart part) { private void showRenameDialog(ModelPart part) {
String newName = (String) JOptionPane.showInputDialog( String newName = (String) JOptionPane.showInputDialog(
this, this,
@@ -360,7 +332,6 @@ public class ModelLayerPanel extends JPanel {
} }
} }
// ============== 原有业务方法(保持不变) ==============
public void setModel(Model2D model) { public void setModel(Model2D model) {
this.model = model; this.model = model;
this.psdImporter = new PSDImporter(model, renderPanel, this); this.psdImporter = new PSDImporter(model, renderPanel, this);
@@ -454,7 +425,6 @@ public class ModelLayerPanel extends JPanel {
} }
} }
// 现代化边框
private TitledBorder createModernBorder(String title) { private TitledBorder createModernBorder(String title) {
TitledBorder border = BorderFactory.createTitledBorder( TitledBorder border = BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(BORDER_COLOR, 1, true), 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) { public ModernButton(String text) {
super(text); super(text);
setupModernStyle(); 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) { public ModernMenuItem(String text) {
super(text); super(text);
setBackground(SURFACE_COLOR); 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() { public ModernPopupMenu() {
setBackground(SURFACE_COLOR); setBackground(SURFACE_COLOR);
setBorder(BorderFactory.createLineBorder(BORDER_COLOR)); setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
} }
} }
// 其余原有方法保持不变...
// (reloadFromModel, performVisualReorder, bindTextureToSelectedPart等)
public void reloadFromModel() { public void reloadFromModel() {
ModelPart selected = layerList.getSelectedValue(); ModelPart selected = layerList.getSelectedValue();
@@ -745,13 +712,11 @@ public class ModelLayerPanel extends JPanel {
String name = JOptionPane.showInputDialog(this, "新图层名称:", f.getName()); String name = JOptionPane.showInputDialog(this, "新图层名称:", f.getName());
if (name == null || name.trim().isEmpty()) name = f.getName(); if (name == null || name.trim().isEmpty()) name = f.getName();
// 创建部件与 Mesh
ModelPart part = model.createPart(name); ModelPart part = model.createPart(name);
Mesh2D mesh = MeshTextureUtil.createQuadForImage(img, name + "_mesh"); Mesh2D mesh = MeshTextureUtil.createQuadForImage(img, name + "_mesh");
mesh.createDefaultSecondaryVertices(); mesh.createDefaultSecondaryVertices();
part.addMesh(mesh); part.addMesh(mesh);
// 创建纹理
if (renderPanel != null) { if (renderPanel != null) {
final String texName = name + "_tex"; final String texName = name + "_tex";
final String filePath = f.getAbsolutePath(); final String filePath = f.getAbsolutePath();

View File

@@ -27,6 +27,7 @@ import java.awt.event.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
@@ -100,6 +101,8 @@ public class ModelRenderPanel extends JPanel {
handleSingleClick(); handleSingleClick();
}); });
doubleClickTimer.setRepeats(false); doubleClickTimer.setRepeats(false);
modelsUpdate(getModel());
} }
/** /**
@@ -541,7 +544,7 @@ public class ModelRenderPanel extends JPanel {
g2d.setColor(Color.DARK_GRAY); g2d.setColor(Color.DARK_GRAY);
g2d.fillRect(0, 0, panelW, panelH); g2d.fillRect(0, 0, panelW, panelH);
} }
if (modelRef.get() == null) { if (getModel() == null) {
g2d.setColor(new Color(255, 255, 0, 200)); g2d.setColor(new Color(255, 255, 0, 200));
g2d.drawString("模型未加载", 10, 20); g2d.drawString("模型未加载", 10, 20);
} }
@@ -550,20 +553,23 @@ public class ModelRenderPanel extends JPanel {
} }
} }
/** 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 void setModel(Model2D model) { }
glContextManager.executeInGLContext(() -> {
modelRef.set(model);
logger.info("模型已更新");
});
} }
/** /**
* 获取当前渲染的模型 * 获取当前渲染的模型
*/ */
public Model2D getModel() { public Model2D getModel() {
if (modelRef.get() == null) {
try {
return glContextManager.waitForModel().get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
return modelRef.get(); return modelRef.get();
} }
@@ -621,7 +627,7 @@ public class ModelRenderPanel extends JPanel {
* 通过网格查找对应的 ModelPart * 通过网格查找对应的 ModelPart
*/ */
public ModelPart findPartByMesh(Mesh2D mesh) { public ModelPart findPartByMesh(Mesh2D mesh) {
Model2D model = modelRef.get(); Model2D model = getModel();
if (model == null) return null; if (model == null) return null;
for (ModelPart part : model.getParts()) { for (ModelPart part : model.getParts()) {
ModelPart found = findPartByMeshRecursive(part, mesh); ModelPart found = findPartByMeshRecursive(part, mesh);

View File

@@ -49,6 +49,8 @@ public class GLContextManager {
private static final float ZOOM_SMOOTHING = 0.18f; // 0..1, 越大收敛越快(建议 0.12-0.25 private static final float ZOOM_SMOOTHING = 0.18f; // 0..1, 越大收敛越快(建议 0.12-0.25
private RepaintCallback repaintCallback; private RepaintCallback repaintCallback;
private final CompletableFuture<Model2D> modelReady = new CompletableFuture<>();
public GLContextManager(String modelPath, int width, int height) { public GLContextManager(String modelPath, int width, int height) {
this.modelPath = modelPath; this.modelPath = modelPath;
this.width = width; this.width = width;
@@ -60,6 +62,9 @@ public class GLContextManager {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.modelRef.set(model); this.modelRef.set(model);
if (model != null && !modelReady.isDone()) {
modelReady.complete(model);
}
} }
public int getHeight() { public int getHeight() {
@@ -134,9 +139,17 @@ public class GLContextManager {
private void loadModelInContext() { private void loadModelInContext() {
try { try {
if (modelPath != null) { if (modelPath != null) {
Model2D model = Model2D.loadFromFile(modelPath); Model2D model;
try {
model = Model2D.loadFromFile(modelPath);
} catch (Throwable e) {
model = new Model2D("新的项目");
}
modelRef.set(model); modelRef.set(model);
logger.info("模型加载成功: {}", modelPath); logger.info("模型加载成功: {}", modelPath);
if (model != null && !modelReady.isDone()) {
modelReady.complete(model);
}
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("模型加载失败: {}", e.getMessage(), e); logger.error("模型加载失败: {}", e.getMessage(), e);
@@ -154,6 +167,10 @@ public class GLContextManager {
} }
renderThread = new Thread(() -> { renderThread = new Thread(() -> {
try { try {
if (modelRef.get() != null && !modelReady.isDone()) {
modelReady.complete(modelRef.get());
}
createOffscreenContext(); createOffscreenContext();
// 等待上下文就绪后再开始渲染循环contextReady 由 createOffscreenContext 完成) // 等待上下文就绪后再开始渲染循环contextReady 由 createOffscreenContext 完成)
@@ -590,4 +607,18 @@ public class GLContextManager {
public void setCameraDragging(boolean cameraDragging) { public void setCameraDragging(boolean cameraDragging) {
this.cameraDragging = cameraDragging; this.cameraDragging = cameraDragging;
} }
/**
* 从 GLContextManager 获取当前模型引用
*/
public Model2D getModel() {
return modelRef.get();
}
/**
* 等待模型加载完成(若已经完成会立即返回已完成的 CompletableFuture
*/
public CompletableFuture<Model2D> waitForModel() {
return modelReady;
}
} }

View File

@@ -227,16 +227,10 @@ public class LiquifyTool extends Tool {
*/ */
private void applyLiquifyEffect(float modelX, float modelY) { private void applyLiquifyEffect(float modelX, float modelY) {
if (liquifyTargetPart == null) return; if (liquifyTargetPart == null) return;
Vector2f brushCenter = new Vector2f(modelX, modelY); Vector2f brushCenter = new Vector2f(modelX, modelY);
// 判断是否按住Ctrl键决定是否创建顶点
boolean createVertices = renderPanel.getKeyboardManager().getIsCtrlPressed(); boolean createVertices = renderPanel.getKeyboardManager().getIsCtrlPressed();
liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize, liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize,
liquifyBrushStrength, currentLiquifyMode, 1, createVertices); liquifyBrushStrength, currentLiquifyMode, 1, createVertices);
// 强制重绘
renderPanel.repaint(); renderPanel.repaint();
} }
@@ -246,7 +240,6 @@ public class LiquifyTool extends Tool {
private boolean isOverTargetMesh(float modelX, float modelY) { private boolean isOverTargetMesh(float modelX, float modelY) {
if (liquifyTargetMesh == null) return false; if (liquifyTargetMesh == null) return false;
// 更新边界框
liquifyTargetMesh.updateBounds(); liquifyTargetMesh.updateBounds();
return liquifyTargetMesh.containsPoint(modelX, modelY); return liquifyTargetMesh.containsPoint(modelX, modelY);
} }

View File

@@ -2055,40 +2055,24 @@ public class ModelPart {
// 1. 保存局部顶点到 originalVertices // 1. 保存局部顶点到 originalVertices
float[] localVertices = mesh.getVertices().clone(); float[] localVertices = mesh.getVertices().clone();
mesh.setOriginalVertices(localVertices); mesh.setOriginalVertices(localVertices);
// 2. 计算世界坐标并写入 *renderVertices*,而不是
// 2. 确保 renderVertices 数组已初始化
// (您需要 Mesh2D.java 中有这个方法)
// mesh.ensureRenderVerticesInitialized();
// 3. 计算世界坐标并写入 *renderVertices*,而不是
int vc = mesh.getVertexCount(); int vc = mesh.getVertexCount();
for (int i = 0; i < vc; i++) { for (int i = 0; i < vc; i++) {
Vector2f local = new Vector2f(localVertices[i * 2], localVertices[i * 2 + 1]); Vector2f local = new Vector2f(localVertices[i * 2], localVertices[i * 2 + 1]);
Vector2f worldPt = Matrix3fUtils.transformPoint(this.worldTransform, local); Vector2f worldPt = Matrix3fUtils.transformPoint(this.worldTransform, local);
// mesh.setVertex(i, worldPt.x, worldPt.y);
// 错误mesh.setVertex(i, worldPt.x, worldPt.y); mesh.setRenderVertex(i, worldPt.x, worldPt.y);
// 正确:
mesh.setRenderVertex(i, worldPt.x, worldPt.y); // 假设 setRenderVertex 存在
} }
// 3. 同步 pivot
// 4. 同步 pivot
try { try {
Vector2f origPivot = mesh.getOriginalPivot(); Vector2f origPivot = mesh.getOriginalPivot();
Vector2f worldPivot = Matrix3fUtils.transformPoint(this.worldTransform, origPivot); Vector2f worldPivot = Matrix3fUtils.transformPoint(this.worldTransform, origPivot);
mesh.setPivot(worldPivot.x, worldPivot.y); // 现在这个会成功因为步骤1的修复 mesh.setPivot(worldPivot.x, worldPivot.y); // 现在这个会成功因为步骤1的修复
} catch (Exception ignored) { } catch (Exception ignored) {
} }
// ==================== 新增:初始化木偶控制点的位置 ====================
initializePuppetPinsPosition(mesh); initializePuppetPinsPosition(mesh);
// 标记为已烘焙到世界坐标(语义上明确),并确保 bounds/dirty 状态被正确刷新
mesh.setBakedToWorld(true); mesh.setBakedToWorld(true);
// 确保 GPU 数据在下一次绘制时会被上传(如果当前在渲染线程,也可以直接 uploadToGPU
mesh.markDirty(); mesh.markDirty();
// 将拷贝加入到本部件
meshes.add(mesh); meshes.add(mesh);
boundsDirty = true; boundsDirty = true;
} }

View File

@@ -30,6 +30,10 @@ public class MeshData implements Serializable {
// 原始顶点数据(用于变形恢复) // 原始顶点数据(用于变形恢复)
public float[] originalVertices; public float[] originalVertices;
public float[] renderVertices = null;
public String modelPartName = null; // 关联的ModelPart名称
public boolean isSuspension = false;
public Vector2f previewPoint = null;
// 变换相关 // 变换相关
public Vector2f pivot; public Vector2f pivot;
@@ -83,6 +87,12 @@ public class MeshData implements Serializable {
// 保存原始顶点数据 // 保存原始顶点数据
this.originalVertices = mesh.getOriginalVertices(); 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()); this.pivot = new Vector2f(mesh.getPivot());
@@ -169,6 +179,11 @@ public class MeshData implements Serializable {
// 恢复二级顶点 // 恢复二级顶点
restoreSecondaryVertices(mesh); restoreSecondaryVertices(mesh);
mesh.setSuspension(this.isSuspension);
if (this.previewPoint != null) {
mesh.setPreviewPoint(new Vector2f(this.previewPoint));
}
// 恢复木偶控制点 // 恢复木偶控制点
restorePuppetPins(mesh); restorePuppetPins(mesh);
@@ -237,6 +252,11 @@ public class MeshData implements Serializable {
copy.bakedToWorld = this.bakedToWorld; copy.bakedToWorld = this.bakedToWorld;
copy.isRenderVertices = this.isRenderVertices; 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; copy.originalVertices = this.originalVertices != null ? this.originalVertices.clone() : null;

View File

@@ -102,7 +102,6 @@ public class Mesh2D {
// ==================== 构造器 ==================== // ==================== 构造器 ====================
private static final float SNAP_THRESHOLD = 0.01f; // 靠近判定阈值(按需要调大,比如 0.1f private static final float SNAP_THRESHOLD = 0.01f; // 靠近判定阈值(按需要调大,比如 0.1f
private SecondaryVertex pinnedController = null; // 当前作为“钉子”的控制点(若有)
public Mesh2D() { public Mesh2D() {
this.name = "unnamed"; this.name = "unnamed";
this.vertices = new float[0]; this.vertices = new float[0];
@@ -1011,7 +1010,6 @@ public class Mesh2D {
v.setPosition(otherPos); v.setPosition(otherPos);
// 把被靠近的那个当作钉子 // 把被靠近的那个当作钉子
other.setPinned(true); other.setPinned(true);
pinnedController = other;
// 把移动到其上的顶点锁定 // 把移动到其上的顶点锁定
v.setLocked(true); v.setLocked(true);
logger.info("SecondaryVertex {} snapped to {}. {} pinned, {} locked.", v.getId(), other.getId(), other.getId(), v.getId()); 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.setPinned(false);
sv.setLocked(false); sv.setLocked(false);
} }
pinnedController = null;
logger.info("All secondary vertices unpinned/unlocked"); logger.info("All secondary vertices unpinned/unlocked");
} }
@@ -2291,6 +2288,10 @@ public class Mesh2D {
return puppetPins.size(); return puppetPins.size();
} }
public boolean isSuspension() {
return isSuspension;
}
/** /**
* 顶点变换器接口 * 顶点变换器接口
*/ */
@@ -2684,15 +2685,10 @@ public class Mesh2D {
* 标记数据已修改 * 标记数据已修改
*/ */
public void markDirty() { public void markDirty() {
// 删除旧 GPU 对象(若有),并标记脏
deleteGPU(); deleteGPU();
this.dirty = true; this.dirty = true;
this.boundsDirty = true; this.boundsDirty = true;
this.multiSelectionDirty = true; this.multiSelectionDirty = true;
// 渲染缓存renderVertices不再可信清除烘焙标志
this.bakedToWorld = false;
// 注意:不立即清除 renderVertices 数组(保留以供 debug但 bakedToWorld=false 可确保上传使用局部 vertices
} }

View File

@@ -98,7 +98,7 @@ public class LiquifyTargetPartRander extends RanderTools {
drawRoundedBoundingOutline(bb, bounds, outline, 1.0f, 0f); 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; boolean large = vertexCount > LARGE_VERTEX_THRESHOLD;
// 边框:始终绘制(合并一次 begin/end // 边框:始终绘制(合并一次 begin/end
@@ -172,13 +172,13 @@ public class LiquifyTargetPartRander extends RanderTools {
// 合并绘制外轮廓(单次 begin/end // 合并绘制外轮廓(单次 begin/end
private void drawOutlineOnce(BufferBuilder bb, Mesh2D mesh2D) { 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); 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); GL11.glLineWidth(1.0f);
bb.begin(GL11.GL_LINE_LOOP, count); bb.begin(GL11.GL_LINE_LOOP, count);
bb.setColor(OUTER_LINE); bb.setColor(OUTER_LINE);
float[] verts = mesh2D.getVertices(); float[] verts = mesh2D.getRenderVertices();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int base = i * 2; int base = i * 2;
bb.vertex(verts[base], verts[base + 1], 0f, 0f); 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 条线) int lines = (idx.length / 3) * 6; // 每三角 6 顶点3 条线)
bb.begin(GL11.GL_LINES, lines); bb.begin(GL11.GL_LINES, lines);
bb.setColor(innerLine); bb.setColor(innerLine);
float[] verts = mesh2D.getVertices(); float[] verts = mesh2D.getRenderVertices();
for (int i = 0; i < idx.length; i += 3) { for (int i = 0; i < idx.length; i += 3) {
int i1 = idx[i]; int i1 = idx[i];
int i2 = idx[i + 1]; int i2 = idx[i + 1];
@@ -215,13 +215,13 @@ public class LiquifyTargetPartRander extends RanderTools {
// 顶点点标记 - 优化版:大网格时采样绘制、减少 segment、合并操作 // 顶点点标记 - 优化版:大网格时采样绘制、减少 segment、合并操作
private void drawLiquifyVertexPointsOptimized(Mesh2D mesh2D, BufferBuilder bb, List<TextItem> pendingTexts, boolean large, int vertexCount) { private void drawLiquifyVertexPointsOptimized(Mesh2D mesh2D, BufferBuilder bb, List<TextItem> 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 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 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); final Vector4f STROKE = new Vector4f(1f, 1f, 1f, large ? 0.75f : 0.9f);
float[] verts = mesh2D.getVertices(); float[] verts = mesh2D.getRenderVertices();
// 采样步长,保证绘制点数量不会超过 TARGET_VERTEX_DRAW越大网格步长越大 // 采样步长,保证绘制点数量不会超过 TARGET_VERTEX_DRAW越大网格步长越大
int step = 1; int step = 1;

View File

@@ -7,6 +7,7 @@ import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart; import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf;
import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -22,7 +23,6 @@ import java.util.List;
public class ModelLayerPanelTest { public class ModelLayerPanelTest {
public static void main(String[] args) { public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
//LookAndFeel defaultLaf = isDarkMode ? : new FlatMacLightLaf();
try { try {
UIManager.setLookAndFeel(new FlatMacDarkLaf()); UIManager.setLookAndFeel(new FlatMacDarkLaf());
} catch (UnsupportedLookAndFeelException e) { } catch (UnsupportedLookAndFeelException e) {
@@ -30,129 +30,79 @@ public class ModelLayerPanelTest {
} }
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8)); System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(System.err, 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 测试(含渲染面板和变换面板)"); JFrame frame = new JFrame("ModelLayerPanel 测试(含渲染面板和变换面板)");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(new BorderLayout()); frame.setLayout(new BorderLayout());
ModelRenderPanel renderPanel = new ModelRenderPanel("C:\\Users\\Administrator\\Desktop\\testing.model", 640, 480);
// 左侧:图层面板(传入 renderPanel 后可在面板中绑定贴图到 GL 上下文) ModelLayerPanel layerPanel = new ModelLayerPanel(renderPanel);
// 先创建一个占位 renderPanel再把它传给 layerPanelModelRenderPanel 构造需要尺寸)
ModelRenderPanel renderPanel = new ModelRenderPanel(model, 640, 480);
ModelLayerPanel layerPanel = new ModelLayerPanel(model, renderPanel);
layerPanel.setPreferredSize(new Dimension(260, 600)); layerPanel.setPreferredSize(new Dimension(260, 600));
frame.add(layerPanel, BorderLayout.WEST); frame.add(layerPanel, BorderLayout.WEST);
// 中间:渲染面板
renderPanel.setPreferredSize(new Dimension(640, 480)); renderPanel.setPreferredSize(new Dimension(640, 480));
frame.add(renderPanel, BorderLayout.CENTER); frame.add(renderPanel, BorderLayout.CENTER);
// 创建变换面板
TransformPanel transformPanel = new TransformPanel(renderPanel); TransformPanel transformPanel = new TransformPanel(renderPanel);
// 右侧:创建选项卡面板,包含模型树和变换面板
JTabbedPane rightTabbedPane = new JTabbedPane(); JTabbedPane rightTabbedPane = new JTabbedPane();
renderPanel.getGlContextManager().waitForModel().thenAccept(model -> {
// 模型树选项卡 if (model == null) return;
JTree tree = new JTree(model.toTreeNode()); JTree tree = new JTree(model.toTreeNode());
JScrollPane treeScroll = new JScrollPane(tree); JScrollPane treeScroll = new JScrollPane(tree);
treeScroll.setPreferredSize(new Dimension(280, 600)); treeScroll.setPreferredSize(new Dimension(280, 600));
rightTabbedPane.addTab("模型结构", treeScroll); rightTabbedPane.addTab("模型结构", treeScroll);
JScrollPane transformScroll = new JScrollPane(transformPanel);
// 变换面板选项卡 transformScroll.setPreferredSize(new Dimension(280, 600));
JScrollPane transformScroll = new JScrollPane(transformPanel); rightTabbedPane.addTab("变换控制", transformScroll);
transformScroll.setPreferredSize(new Dimension(280, 600)); rightTabbedPane.setPreferredSize(new Dimension(300, 600));
rightTabbedPane.addTab("变换控制", transformScroll); frame.add(rightTabbedPane, BorderLayout.EAST);
JPanel bottom = getBottom(renderPanel, transformPanel);
rightTabbedPane.setPreferredSize(new Dimension(300, 600)); frame.add(bottom, BorderLayout.SOUTH);
frame.add(rightTabbedPane, BorderLayout.EAST); renderPanel.addModelClickListener((mesh, modelX, modelY, screenX, screenY) -> {
if (mesh == null) return;
// 底部:演示按钮(刷新树以反映面板中对模型的更改) System.out.println("点击了模型:" + mesh.getName() + ",模型坐标:" + modelX + ", " + modelY + ",屏幕坐标:" + screenX + ", " + screenY);
JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT)); List<ModelPart> selectedPart = renderPanel.getSelectedParts();
JButton refreshBtn = new JButton("刷新模型树"); transformPanel.setSelectedParts(selectedPart);
refreshBtn.addActionListener(e -> { rightTabbedPane.setSelectedIndex(1);
tree.setModel(new javax.swing.tree.DefaultTreeModel(model.toTreeNode())); });
for (int i = 0; i < tree.getRowCount(); i++) tree.expandRow(i); frame.addWindowListener(new java.awt.event.WindowAdapter() {
// 同步通知渲染面板(如果需要)去刷新模型 @Override
try { public void windowClosed(java.awt.event.WindowEvent e) {
renderPanel.setModel(model); try {
} catch (Exception ignored) { 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("打印部件顺序(控制台)"); private static @NotNull JPanel getBottom(ModelRenderPanel renderPanel, TransformPanel transformPanel) {
printOrderBtn.addActionListener(e -> { JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT));
System.out.println("当前模型部件顺序:");
JButton printOrderBtn = new JButton("打印部件顺序(控制台)");
printOrderBtn.addActionListener(e -> {
System.out.println("当前模型部件顺序:");
renderPanel.getGlContextManager().waitForModel().thenAccept(model -> {
if (model == null) return;
for (ModelPart p : model.getParts()) { for (ModelPart p : model.getParts()) {
System.out.println(" - " + p.getName() + " (可见=" + p.isVisible() + ", 不透明度=" + p.getOpacity() + ")"); System.out.println(" - " + p.getName() + " (可见=" + p.isVisible() + ", 不透明度=" + p.getOpacity() + ")");
} }
}); });
bottom.add(printOrderBtn); });
bottom.add(printOrderBtn);
// 添加选中部件更新按钮 // 添加选中部件更新按钮
JButton updateSelectionBtn = new JButton("更新选中部件"); JButton updateSelectionBtn = new JButton("更新选中部件");
updateSelectionBtn.addActionListener(e -> { updateSelectionBtn.addActionListener(e -> {
renderPanel.getGlContextManager().executeInGLContext(() -> { renderPanel.getGlContextManager().executeInGLContext(() -> {
List<ModelPart> 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);
// 自动更新变换面板的选中部件
List<ModelPart> selectedPart = renderPanel.getSelectedParts(); List<ModelPart> selectedPart = renderPanel.getSelectedParts();
transformPanel.setSelectedParts(selectedPart); 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;
} }
} }