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() {
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;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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再把它传给 layerPanelModelRenderPanel 构造需要尺寸)
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;
}
}