feat(render): 实现模型异步加载与渲染优化
- 在 GLContextManager 中添加模型加载完成的 CompletableFuture 支持- 优化 LiquifyTargetPartRander 使用 renderVertices 替代 vertices- 移除 LiquifyTool 中冗余的 Ctrl 键判断与强制重绘逻辑 - Mesh2D 中移除已废弃的 pinnedController 字段 - MeshData 中新增 renderVertices、isSuspension 等渲染相关字段- ModelLayerPanel 支持模型异步加载完成后的初始化 - ModelRenderPanel 添加模型获取的同步等待机制 - 清理大量冗余注释与无用代码,提升代码可读性
This commit is contained in:
@@ -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<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights();
|
||||
int idx = 0;
|
||||
|
||||
@@ -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<ModelPart> listModel;
|
||||
private JList<ModelPart> 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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Model2D> 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<Model2D> waitForModel() {
|
||||
return modelReady;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<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 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;
|
||||
|
||||
@@ -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<ModelPart> 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<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);
|
||||
|
||||
// 自动更新变换面板的选中部件
|
||||
// 添加选中部件更新按钮
|
||||
JButton updateSelectionBtn = new JButton("更新选中部件");
|
||||
updateSelectionBtn.addActionListener(e -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
List<ModelPart> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user