diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java index 41011b0..3a7f177 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/ModelRenderPanel.java @@ -1,19 +1,22 @@ package com.chuangzhou.vivid2D.render.awt; import com.chuangzhou.vivid2D.render.ModelRender; -import com.chuangzhou.vivid2D.render.awt.manager.GLContextManager; +import com.chuangzhou.vivid2D.render.awt.manager.*; +import com.chuangzhou.vivid2D.render.awt.tools.PuppetDeformationTool; +import com.chuangzhou.vivid2D.render.awt.tools.SelectionTool; +import com.chuangzhou.vivid2D.render.awt.tools.Tool; +import com.chuangzhou.vivid2D.render.awt.tools.VertexDeformationTool; +import com.chuangzhou.vivid2D.render.awt.tools.LiquifyTool; import com.chuangzhou.vivid2D.render.awt.util.OperationHistoryGlobal; -import com.chuangzhou.vivid2D.render.awt.util.OperationHistoryManager; import com.chuangzhou.vivid2D.render.model.Model2D; import com.chuangzhou.vivid2D.render.model.ModelPart; -import com.chuangzhou.vivid2D.render.model.util.BoundingBox; import com.chuangzhou.vivid2D.render.model.util.Mesh2D; -import com.chuangzhou.vivid2D.render.model.util.PuppetPin; -import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex; +import com.chuangzhou.vivid2D.render.model.util.manager.RanderToolsManager; +import com.chuangzhou.vivid2D.render.model.util.tools.LiquifyTargetPartRander; +import com.chuangzhou.vivid2D.render.model.util.tools.PuppetDeformationRander; +import com.chuangzhou.vivid2D.render.model.util.tools.VertexDeformationRander; import com.chuangzhou.vivid2D.render.systems.Camera; import com.chuangzhou.vivid2D.test.TestModelGLPanel; -import org.joml.Matrix3f; -import org.joml.Vector2f; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,10 +25,8 @@ import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; -import java.lang.reflect.Method; import java.util.List; -import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; /** @@ -41,104 +42,23 @@ import java.util.concurrent.atomic.AtomicReference; */ public class ModelRenderPanel extends JPanel { private static final Logger logger = LoggerFactory.getLogger(ModelRenderPanel.class); + private final GLContextManager glContextManager; + private final MouseManagement mouseManagement; + private final CameraManagement cameraManagement; + private final WorldManagement worldManagement; private final AtomicReference modelRef = new AtomicReference<>(); - + private final KeyboardManager keyboardManager; private final CopyOnWriteArrayList clickListeners = new CopyOnWriteArrayList<>(); - private volatile Mesh2D hoveredMesh = null; - // 修改:将单个选中改为集合,支持多选 - private final Set selectedMeshes = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private volatile Mesh2D lastSelectedMesh = null; // 用于Shift多选的最后一个选中项 - private volatile ModelPart draggedPart = null; - private volatile float dragStartX, dragStartY; - private volatile float partStartX, partStartY; - private volatile boolean isDragging = false; - - public enum DragMode { - NONE, // 无拖拽 - MOVE, // 移动部件 - RESIZE_LEFT, // 调整左边 - RESIZE_RIGHT, // 调整右边 - RESIZE_TOP, // 调整上边 - RESIZE_BOTTOM, // 调整下边 - RESIZE_TOP_LEFT, // 调整左上角 - RESIZE_TOP_RIGHT, // 调整右上角 - RESIZE_BOTTOM_LEFT, // 调整左下角 - RESIZE_BOTTOM_RIGHT, // 调整右下角 - ROTATE, // 新增:旋转 - MOVE_PIVOT, // 新增:移动中心点 - MOVE_SECONDARY_VERTEX, // 新增:移动二级顶点 - MOVE_PUPPET_PIN // 新增:移动 puppetPin - } - - // 新增:拖拽相关字段 - private volatile DragMode currentDragMode = DragMode.NONE; - private volatile float resizeStartWidth, resizeStartHeight; - private volatile boolean shiftPressed = false; - private volatile boolean ctrlPressed = false; // 新增:Ctrl键状态 - - // 新增:选择框边框厚度和角点大小 + private final StatusRecordManagement statusRecordManagement; + private final RanderToolsManager randerToolsManager = RanderToolsManager.getInstance(); public static final float BORDER_THICKNESS = 6.0f; public static final float CORNER_SIZE = 12.0f; - private static final float ZOOM_STEP = 1.15f; // 每格滚轮的指数因子(>1 放大) - private static final float ZOOM_MIN = 0.1f; - private static final float ZOOM_MAX = 8.0f; - private volatile boolean shiftDuringDrag = false; - private volatile float rotationStartAngle = 0.0f; - private final Vector2f rotationCenter = new Vector2f(); - private static final float ROTATION_HANDLE_DISTANCE = 30.0f; - private OperationHistoryManager historyManager; - - // 新增:操作历史管理器 - private final OperationHistoryGlobal operationHistory; - - // 新增:拖拽操作的状态记录字段 - private final Map dragStartPositions = new HashMap<>(); - private final Map dragStartScales = new HashMap<>(); - private final Map dragStartRotations = new HashMap<>(); - private final Map dragStartPivots = new HashMap<>(); - - // 新增:鼠标手势相关字段 - private volatile Cursor currentCursor = Cursor.getDefaultCursor(); - private final DragMode hoverDragMode = DragMode.NONE; - private volatile boolean isOverSelection = false; - // ================== 摄像机控制相关字段 ================== - - - private volatile int lastCameraDragX, lastCameraDragY; - private volatile float cameraStartX, cameraStartY; - private static final float CAMERA_ZOOM_STEP = 1.1f; - private static final float CAMERA_ZOOM_MIN = 0.1f; - private static final float CAMERA_ZOOM_MAX = 10.0f; - private static final float CAMERA_Z_STEP = 0.1f; - private static final float CAMERA_Z_MIN = -5.0f; - private static final float CAMERA_Z_MAX = 5.0f; - - private volatile boolean liquifyMode = false; - private volatile ModelPart liquifyTargetPart = null; - private volatile Mesh2D liquifyTargetMesh = null; private final Timer doubleClickTimer; private volatile long lastClickTime = 0; private static final int DOUBLE_CLICK_INTERVAL = 300; // 双击间隔(毫秒) - - private float liquifyBrushSize = 50.0f; - private float liquifyBrushStrength = 2.0f; - private ModelPart.LiquifyMode currentLiquifyMode = ModelPart.LiquifyMode.PUSH; - - // ================== 二级顶点变形器相关字段 ================== - private volatile boolean secondaryVertexMode = false; // 二级顶点变形模式 - private volatile Mesh2D secondaryVertexTargetMesh = null; // 当前操作的二级顶点网格 - private volatile SecondaryVertex selectedSecondaryVertex = null; // 选中的二级顶点 - private volatile SecondaryVertex hoveredSecondaryVertex = null; // 悬停的二级顶点 - private static final float SECONDARY_VERTEX_TOLERANCE = 8.0f; // 二级顶点选择容差 - - // ================== 木偶工具相关字段 ================== - private volatile boolean puppetMode = false; // 木偶工具模式 - private volatile Mesh2D puppetTargetMesh = null; // 当前操作的木偶工具网格 - private volatile PuppetPin selectedPuppetPin = null; // 选中的木偶控制点 - private volatile PuppetPin hoveredPuppetPin = null; // 悬停的木偶控制点 - private static final float PUPPET_PIN_TOLERANCE = 8.0f; // 木偶控制点选择容差 + private final ToolManagement toolManagement; // ================== 摄像机控制方法 ================== @@ -153,7 +73,7 @@ public class ModelRenderPanel extends JPanel { * 重置摄像机 */ public void resetCamera() { - glContextManager.executeInGLContext(() -> ModelRender.resetCamera()); + glContextManager.executeInGLContext(ModelRender::resetCamera); } /** @@ -161,9 +81,20 @@ public class ModelRenderPanel extends JPanel { */ public ModelRenderPanel(String modelPath, int width, int height) { this.glContextManager = new GLContextManager(modelPath, width, height); - this.operationHistory = OperationHistoryGlobal.getInstance(); + this.statusRecordManagement = new StatusRecordManagement(this, OperationHistoryGlobal.getInstance()); + this.keyboardManager = new KeyboardManager(this); + this.worldManagement = new WorldManagement(this, glContextManager); + this.cameraManagement = new CameraManagement(this, glContextManager, worldManagement); + this.mouseManagement = new MouseManagement(this, glContextManager, cameraManagement, keyboardManager); + this.toolManagement = new ToolManagement(this, randerToolsManager); + + // 注册所有工具 + toolManagement.registerTool(new PuppetDeformationTool(this),new PuppetDeformationRander()); + toolManagement.registerTool(new VertexDeformationTool(this),new VertexDeformationRander()); + toolManagement.registerTool(new LiquifyTool(this), new LiquifyTargetPartRander()); initialize(); - initKeyboardShortcuts(); + + keyboardManager.initKeyboardShortcuts(); doubleClickTimer = new Timer(DOUBLE_CLICK_INTERVAL, e -> { handleSingleClick(); @@ -177,9 +108,20 @@ public class ModelRenderPanel extends JPanel { public ModelRenderPanel(Model2D model, int width, int height) { this.glContextManager = new GLContextManager(model, width, height); this.modelRef.set(model); - this.operationHistory = OperationHistoryGlobal.getInstance(); + this.statusRecordManagement = new StatusRecordManagement(this, OperationHistoryGlobal.getInstance()); + this.keyboardManager = new KeyboardManager(this); + this.worldManagement = new WorldManagement(this, glContextManager); + this.cameraManagement = new CameraManagement(this, glContextManager, worldManagement); + this.mouseManagement = new MouseManagement(this, glContextManager, cameraManagement, keyboardManager); + this.toolManagement = new ToolManagement(this, randerToolsManager); + + // 注册所有工具 + toolManagement.registerTool(new PuppetDeformationTool(this),new PuppetDeformationRander()); + toolManagement.registerTool(new VertexDeformationTool(this),new VertexDeformationRander()); + toolManagement.registerTool(new LiquifyTool(this), new LiquifyTargetPartRander()); + initialize(); - initKeyboardShortcuts(); + keyboardManager.initKeyboardShortcuts(); doubleClickTimer = new Timer(DOUBLE_CLICK_INTERVAL, e -> { // 单单击超时处理 handleSingleClick(); @@ -187,1086 +129,26 @@ public class ModelRenderPanel extends JPanel { doubleClickTimer.setRepeats(false); } - /** - * 初始化键盘快捷键 - */ - private void initKeyboardShortcuts() { - // 获取输入映射和动作映射 - InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - ActionMap actionMap = getActionMap(); - - // 撤回快捷键:Ctrl+Z - KeyStroke undoKey = KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_DOWN_MASK); - inputMap.put(undoKey, "undo"); - actionMap.put("undo", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - undo(); - } - }); - - // 木偶工具快捷键 Ctrl+P - KeyStroke puppetModeKey = KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK); - inputMap.put(puppetModeKey, "puppetMode"); - actionMap.put("puppetMode", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - togglePuppetMode(); - } - }); - - // 重做快捷键:Ctrl+Y 或 Ctrl+Shift+Z - KeyStroke redoKey1 = KeyStroke.getKeyStroke(KeyEvent.VK_Y, KeyEvent.CTRL_DOWN_MASK); - KeyStroke redoKey2 = KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); - inputMap.put(redoKey1, "redo"); - inputMap.put(redoKey2, "redo"); - actionMap.put("redo", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - redo(); - } - }); - - // 清除历史记录:Ctrl+Shift+Delete - KeyStroke clearHistoryKey = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); - inputMap.put(clearHistoryKey, "clearHistory"); - actionMap.put("clearHistory", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - clearHistory(); - } - }); - - // 添加摄像机重置快捷键:Ctrl+R - KeyStroke resetCameraKey = KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK); - inputMap.put(resetCameraKey, "resetCamera"); - actionMap.put("resetCamera", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - resetCamera(); - logger.info("重置摄像机"); - } - }); - - // 添加摄像机启用/禁用快捷键:Ctrl+E - KeyStroke toggleCameraKey = KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_DOWN_MASK); - inputMap.put(toggleCameraKey, "toggleCamera"); - actionMap.put("toggleCamera", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - Camera camera = ModelRender.getCamera(); - boolean newState = !camera.isEnabled(); - camera.setEnabled(newState); - logger.info("{}摄像机", newState ? "启用" : "禁用"); - } - }); - - // 新增:二级顶点变形器快捷键 Ctrl+T - KeyStroke secondaryVertexKey = KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.CTRL_DOWN_MASK); - inputMap.put(secondaryVertexKey, "secondaryVertex"); - actionMap.put("secondaryVertex", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - toggleSecondaryVertexMode(); - } - }); - } - - /** - * 切换木偶工具模式 - */ - private void togglePuppetMode() { - if (puppetMode) { - exitPuppetMode(); - } else { - glContextManager.executeInGLContext(this::enterPuppetMode); - } - } - - /** - * 进入木偶工具模式 - */ - private void enterPuppetMode() { - if (selectedMeshes.isEmpty()) { - logger.warn("请先选择一个网格以进入木偶工具模式"); - return; - } - - puppetMode = true; - puppetTargetMesh = lastSelectedMesh; - - if (puppetTargetMesh != null) { - // 显示木偶控制点 - puppetTargetMesh.setShowPuppetPins(true); - - // 如果没有木偶控制点,创建默认的四个角点 - if (puppetTargetMesh.getPuppetPinCount() == 0) { - createDefaultPuppetPins(); - } - - // 预计算权重 - puppetTargetMesh.precomputeAllPuppetWeights(); - } - - // 设置特殊光标 - setCursor(createPuppetCursor()); - - logger.info("进入木偶工具模式: {}", - puppetTargetMesh != null ? puppetTargetMesh.getName() : "null"); - - // 通知监听器 - for (ModelClickListener listener : clickListeners) { - try { - listener.onPuppetModeEntered(puppetTargetMesh); - } catch (Exception ex) { - logger.error("木偶工具模式进入事件监听器执行出错", ex); - } - } - - repaint(); - } - - /** - * 退出木偶工具模式 - */ - private void exitPuppetMode() { - glContextManager.executeInGLContext(() -> { - puppetMode = false; - if (puppetTargetMesh != null) { - puppetTargetMesh.setShowPuppetPins(false); - } - puppetTargetMesh = null; - selectedPuppetPin = null; - hoveredPuppetPin = null; - - // 恢复默认光标 - updateCursorForHoverState(); - - logger.info("退出木偶工具模式"); - - // 通知监听器 - for (ModelClickListener listener : clickListeners) { - try { - listener.onPuppetModeExited(); - } catch (Exception ex) { - logger.error("木偶工具模式退出事件监听器执行出错", ex); - } - } - - repaint(); - }); - } - - /** - * 创建默认的四个角点木偶控制点 - */ - private void createDefaultPuppetPins() { - if (puppetTargetMesh == null) return; - - BoundingBox bounds = puppetTargetMesh.getBounds(); - if (bounds == null || !bounds.isValid()) return; - - float minX = bounds.getMinX(); - float minY = bounds.getMinY(); - float maxX = bounds.getMaxX(); - float maxY = bounds.getMaxY(); - - // 创建四个角点 - puppetTargetMesh.addPuppetPin(minX, minY, 0.0f, 1.0f); // 左下 - puppetTargetMesh.addPuppetPin(maxX, minY, 1.0f, 1.0f); // 右下 - puppetTargetMesh.addPuppetPin(maxX, maxY, 1.0f, 0.0f); // 右上 - puppetTargetMesh.addPuppetPin(minX, maxY, 0.0f, 0.0f); // 左上 - - logger.debug("为网格 {} 创建了4个默认木偶控制点", puppetTargetMesh.getName()); - } - - /** - * 创建木偶工具模式光标 - */ - private Cursor createPuppetCursor() { - int size = 32; - BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = cursorImg.createGraphics(); - - // 设置抗锯齿 - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 绘制透明背景 - g2d.setColor(new Color(0, 0, 0, 0)); - g2d.fillRect(0, 0, size, size); - - // 绘制木偶图标 - int center = size / 2; - - // 外圈(蓝色) - g2d.setColor(Color.BLUE); - g2d.setStroke(new BasicStroke(2f)); - g2d.drawOval(center - 6, center - 6, 12, 12); - - // 内圈 - g2d.setColor(new Color(0, 0, 200, 150)); - g2d.setStroke(new BasicStroke(1f)); - g2d.drawOval(center - 3, center - 3, 6, 6); - - // 中心点 - g2d.setColor(Color.BLUE); - g2d.fillOval(center - 1, center - 1, 2, 2); - - g2d.dispose(); - - return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "PuppetCursor"); - } - - /** - * 在木偶工具模式下处理双击 - */ - private void handlePuppetDoubleClick(MouseEvent e) { - final int screenX = e.getX(); - final int screenY = e.getY(); - - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - // 检查是否双击了木偶控制点 - PuppetPin clickedPin = findPuppetPinAtPosition(modelX, modelY); - if (clickedPin != null) { - // 双击木偶控制点:删除该控制点 - deletePuppetPin(clickedPin); - } else { - // 双击空白处:创建新的木偶控制点 - createPuppetPinAt(modelX, modelY); - } - - } catch (Exception ex) { - logger.error("处理木偶工具双击时出错", ex); - } - }); - } - - /** - * 在指定位置创建木偶控制点 - */ - private void createPuppetPinAt(float x, float y) { - if (puppetTargetMesh == null) return; - - // 计算UV坐标(基于边界框) - BoundingBox bounds = puppetTargetMesh.getBounds(); - if (bounds == null || !bounds.isValid()) return; - - float u = (x - bounds.getMinX()) / bounds.getWidth(); - float v = (y - bounds.getMinY()) / bounds.getHeight(); - - // 限制UV在0-1范围内 - u = Math.max(0.0f, Math.min(1.0f, u)); - v = Math.max(0.0f, Math.min(1.0f, v)); - - PuppetPin newPin = puppetTargetMesh.addPuppetPin(x, y, u, v); - logger.info("创建木偶控制点: ID={}, 位置({}, {}), UV({}, {})", - newPin.getId(), x, y, u, v); - - repaint(); - } - - /** - * 删除木偶控制点 - */ - private void deletePuppetPin(PuppetPin pin) { - if (puppetTargetMesh == null || pin == null) return; - - boolean removed = puppetTargetMesh.removePuppetPin(pin); - if (removed) { - if (selectedPuppetPin == pin) { - selectedPuppetPin = null; - } - if (hoveredPuppetPin == pin) { - hoveredPuppetPin = null; - } - logger.info("删除木偶控制点: ID={}", pin.getId()); - } - - repaint(); - } - - /** - * 在指定位置查找木偶控制点 - */ - private PuppetPin findPuppetPinAtPosition(float x, float y) { - if (puppetTargetMesh == null) return null; - - float tolerance = PUPPET_PIN_TOLERANCE / calculateScaleFactor(); - - return puppetTargetMesh.selectPuppetPinAt(x, y, tolerance); - } - - /** - * 处理木偶工具模式下的鼠标移动 - */ - private void handlePuppetMouseMove(float modelX, float modelY) { - if (!puppetMode || puppetTargetMesh == null) return; - - // 更新悬停的木偶控制点 - PuppetPin newHoveredPin = findPuppetPinAtPosition(modelX, modelY); - - if (newHoveredPin != hoveredPuppetPin) { - hoveredPuppetPin = newHoveredPin; - - // 更新光标 - if (hoveredPuppetPin != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else { - setCursor(createPuppetCursor()); - } - } - } - - /** - * 处理木偶工具模式下的鼠标按下 - */ - private void handlePuppetMousePressed(float modelX, float modelY) { - if (!puppetMode || puppetTargetMesh == null) return; - - // 选择木偶控制点 - PuppetPin clickedPin = findPuppetPinAtPosition(modelX, modelY); - if (clickedPin != null) { - puppetTargetMesh.setSelectedPuppetPin(clickedPin); - selectedPuppetPin = clickedPin; - - // 开始拖拽 - currentDragMode = DragMode.MOVE_PUPPET_PIN; - dragStartX = modelX; - dragStartY = modelY; - - logger.debug("开始移动木偶控制点: ID={}", clickedPin.getId()); - } else { - // 点击空白处,取消选择 - puppetTargetMesh.setSelectedPuppetPin(null); - selectedPuppetPin = null; - } - } - - /** - * 处理木偶工具模式下的鼠标拖拽 - */ - private void handlePuppetMouseDragged(float modelX, float modelY) { - if (!puppetMode || selectedPuppetPin == null) return; - - if (currentDragMode == DragMode.MOVE_PUPPET_PIN) { - float deltaX = modelX - dragStartX; - float deltaY = modelY - dragStartY; - - if (ctrlPressed) { - // Ctrl键按下:精确移动木偶控制点 - selectedPuppetPin.move(deltaX, deltaY); - } else { - // 普通移动:直接设置位置 - selectedPuppetPin.setPosition(modelX, modelY); - } - - // 更新拖拽起始位置 - dragStartX = modelX; - dragStartY = modelY; - - // 更新网格变形 - puppetTargetMesh.updateVerticesFromPuppetPins(); - puppetTargetMesh.markDirty(); - - repaint(); - } - } - - // ================== 二级顶点变形器方法 ================== - - /** - * 切换二级顶点变形模式 - */ - private void toggleSecondaryVertexMode() { - if (secondaryVertexMode) { - exitSecondaryVertexMode(); - } else { - glContextManager.executeInGLContext(this::enterSecondaryVertexMode); - } - } - - /** - * 进入二级顶点变形模式 - */ - private void enterSecondaryVertexMode() { - if (selectedMeshes.isEmpty()) { - logger.warn("请先选择一个网格以进入二级顶点变形模式"); - return; - } - - secondaryVertexMode = true; - secondaryVertexTargetMesh = lastSelectedMesh; - - if (secondaryVertexTargetMesh != null) { - // 显示二级顶点 - secondaryVertexTargetMesh.setShowSecondaryVertices(true); - - // 如果没有二级顶点,创建默认的四个角点 - if (secondaryVertexTargetMesh.getSecondaryVertexCount() == 0) { - createDefaultSecondaryVertices(); - } - } - - // 设置特殊光标 - setCursor(createSecondaryVertexCursor()); - - logger.info("进入二级顶点变形模式: {}", - secondaryVertexTargetMesh != null ? secondaryVertexTargetMesh.getName() : "null"); - - // 通知监听器 - for (ModelClickListener listener : clickListeners) { - try { - listener.onSecondaryVertexModeEntered(secondaryVertexTargetMesh); - } catch (Exception ex) { - logger.error("二级顶点模式进入事件监听器执行出错", ex); - } - } - - repaint(); - } - - /** - * 退出二级顶点变形模式 - */ - private void exitSecondaryVertexMode() { - glContextManager.executeInGLContext(() -> { - secondaryVertexMode = false; - if (secondaryVertexTargetMesh != null) { - secondaryVertexTargetMesh.setShowSecondaryVertices(false); - } - secondaryVertexTargetMesh = null; - selectedSecondaryVertex = null; - hoveredSecondaryVertex = null; - - // 恢复默认光标 - updateCursorForHoverState(); - - logger.info("退出二级顶点变形模式"); - - // 通知监听器 - for (ModelClickListener listener : clickListeners) { - try { - listener.onSecondaryVertexModeExited(); - } catch (Exception ex) { - logger.error("二级顶点模式退出事件监听器执行出错", ex); - } - } - - repaint(); - }); - } - - /** - * 创建默认的四个角点二级顶点 - */ - private void createDefaultSecondaryVertices() { - if (secondaryVertexTargetMesh == null) return; - - BoundingBox bounds = secondaryVertexTargetMesh.getBounds(); - if (bounds == null || !bounds.isValid()) return; - - float minX = bounds.getMinX(); - float minY = bounds.getMinY(); - float maxX = bounds.getMaxX(); - float maxY = bounds.getMaxY(); - - // 创建四个角点 - secondaryVertexTargetMesh.addSecondaryVertex(minX, minY, 0.0f, 1.0f); // 左下 - secondaryVertexTargetMesh.addSecondaryVertex(maxX, minY, 1.0f, 1.0f); // 右下 - secondaryVertexTargetMesh.addSecondaryVertex(maxX, maxY, 1.0f, 0.0f); // 右上 - secondaryVertexTargetMesh.addSecondaryVertex(minX, maxY, 0.0f, 0.0f); // 左上 - - logger.debug("为网格 {} 创建了4个默认二级顶点", secondaryVertexTargetMesh.getName()); - } - - /** - * 创建二级顶点模式光标 - */ - private Cursor createSecondaryVertexCursor() { - int size = 32; - BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = cursorImg.createGraphics(); - - // 设置抗锯齿 - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 绘制透明背景 - g2d.setColor(new Color(0, 0, 0, 0)); - g2d.fillRect(0, 0, size, size); - - // 绘制顶点图标 - int center = size / 2; - - // 外圈 - g2d.setColor(Color.GREEN); - g2d.setStroke(new BasicStroke(2f)); - g2d.drawOval(center - 6, center - 6, 12, 12); - - // 内圈 - g2d.setColor(new Color(0, 200, 0, 150)); - g2d.setStroke(new BasicStroke(1f)); - g2d.drawOval(center - 3, center - 3, 6, 6); - - // 中心点 - g2d.setColor(Color.GREEN); - g2d.fillOval(center - 1, center - 1, 2, 2); - - g2d.dispose(); - - return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "SecondaryVertexCursor"); - } - - /** - * 在二级顶点模式下处理双击 - */ - private void handleSecondaryVertexDoubleClick(MouseEvent e) { - final int screenX = e.getX(); - final int screenY = e.getY(); - - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - // 检查是否双击了二级顶点 - SecondaryVertex clickedVertex = findSecondaryVertexAtPosition(modelX, modelY); - if (clickedVertex != null) { - // 双击二级顶点:删除该顶点 - deleteSecondaryVertex(clickedVertex); - } else { - // 双击空白处:创建新的二级顶点 - createSecondaryVertexAt(modelX, modelY); - } - - } catch (Exception ex) { - logger.error("处理二级顶点双击时出错", ex); - } - }); - } - - /** - * 在指定位置创建二级顶点 - */ - private void createSecondaryVertexAt(float x, float y) { - if (secondaryVertexTargetMesh == null) return; - - // 计算UV坐标(基于边界框) - BoundingBox bounds = secondaryVertexTargetMesh.getBounds(); - if (bounds == null || !bounds.isValid()) return; - - float u = (x - bounds.getMinX()) / bounds.getWidth(); - float v = (y - bounds.getMinY()) / bounds.getHeight(); - - // 限制UV在0-1范围内 - u = Math.max(0.0f, Math.min(1.0f, u)); - v = Math.max(0.0f, Math.min(1.0f, v)); - - SecondaryVertex newVertex = secondaryVertexTargetMesh.addSecondaryVertex(x, y, u, v); - logger.info("创建二级顶点: ID={}, 位置({}, {}), UV({}, {})", - newVertex.getId(), x, y, u, v); - - repaint(); - } - - /** - * 删除二级顶点 - */ - private void deleteSecondaryVertex(SecondaryVertex vertex) { - if (secondaryVertexTargetMesh == null || vertex == null) return; - - boolean removed = secondaryVertexTargetMesh.removeSecondaryVertex(vertex); - if (removed) { - if (selectedSecondaryVertex == vertex) { - selectedSecondaryVertex = null; - } - if (hoveredSecondaryVertex == vertex) { - hoveredSecondaryVertex = null; - } - logger.info("删除二级顶点: ID={}", vertex.getId()); - } - - repaint(); - } - - /** - * 在指定位置查找二级顶点 - */ - private SecondaryVertex findSecondaryVertexAtPosition(float x, float y) { - if (secondaryVertexTargetMesh == null) return null; - - float tolerance = SECONDARY_VERTEX_TOLERANCE / calculateScaleFactor(); - - return secondaryVertexTargetMesh.selectSecondaryVertexAt(x, y, tolerance); - } - - /** - * 处理二级顶点模式下的鼠标移动 - */ - private void handleSecondaryVertexMouseMove(float modelX, float modelY) { - if (!secondaryVertexMode || secondaryVertexTargetMesh == null) return; - - // 更新悬停的二级顶点 - SecondaryVertex newHoveredVertex = findSecondaryVertexAtPosition(modelX, modelY); - - if (newHoveredVertex != hoveredSecondaryVertex) { - hoveredSecondaryVertex = newHoveredVertex; - - // 更新光标 - if (hoveredSecondaryVertex != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else { - setCursor(createSecondaryVertexCursor()); - } - } - } - - /** - * 处理二级顶点模式下的鼠标按下 - */ - private void handleSecondaryVertexMousePressed(float modelX, float modelY) { - if (!secondaryVertexMode || secondaryVertexTargetMesh == null) return; - - // 选择二级顶点 - SecondaryVertex clickedVertex = findSecondaryVertexAtPosition(modelX, modelY); - if (clickedVertex != null) { - secondaryVertexTargetMesh.setSelectedSecondaryVertex(clickedVertex); - selectedSecondaryVertex = clickedVertex; - - // 开始拖拽 - currentDragMode = DragMode.MOVE_SECONDARY_VERTEX; - dragStartX = modelX; - dragStartY = modelY; - - logger.debug("开始移动二级顶点: ID={}", clickedVertex.getId()); - } else { - // 点击空白处,取消选择 - secondaryVertexTargetMesh.setSelectedSecondaryVertex(null); - selectedSecondaryVertex = null; - } - } - - /** - * 处理二级顶点模式下的鼠标拖拽 - */ - private void handleSecondaryVertexMouseDragged(float modelX, float modelY) { - if (!secondaryVertexMode || selectedSecondaryVertex == null) return; - - if (currentDragMode == DragMode.MOVE_SECONDARY_VERTEX) { - float deltaX = modelX - dragStartX; - float deltaY = modelY - dragStartY; - - if (ctrlPressed) { - // Ctrl键按下:精确移动二级顶点 - selectedSecondaryVertex.move(deltaX, deltaY); - } else { - // 普通移动:直接设置位置 - selectedSecondaryVertex.setPosition(modelX, modelY); - } - - // 更新拖拽起始位置 - dragStartX = modelX; - dragStartY = modelY; - - secondaryVertexTargetMesh.markDirty(); - - repaint(); - } - } - /** * 处理双击事件 */ private void handleDoubleClick(MouseEvent e) { - if (secondaryVertexMode) { - handleSecondaryVertexDoubleClick(e); + float[] modelCoords = worldManagement.screenToModelCoordinates(e.getX(), e.getY()); + + // 如果有激活的工具,优先交给工具处理 + if (toolManagement.hasActiveTool() && modelCoords != null) { + glContextManager.executeInGLContext(() -> { + toolManagement.handleMouseDoubleClicked(e, modelCoords[0], modelCoords[1]); + }); return; } - - if (liquifyMode) { - // 如果在液化模式下双击,退出液化模式 - exitLiquifyMode(); - return; - } - - final int screenX = e.getX(); - final int screenY = e.getY(); - - glContextManager.executeInGLContext(() -> { - try { - // 转换屏幕坐标到模型坐标 - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - if (puppetMode) { - handlePuppetDoubleClick(e); - return; - } - - // 检测双击的网格 - Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY); - if (clickedMesh != null) { - enterLiquifyMode(clickedMesh); - } - - } catch (Exception ex) { - logger.error("处理双击时出错", ex); - } - }); - } - - /** - * 进入液化模式 - */ - private void enterLiquifyMode(Mesh2D targetMesh) { - liquifyMode = true; - liquifyTargetMesh = targetMesh; - liquifyTargetPart = findPartByMesh(targetMesh); - - if (liquifyTargetPart != null) { - liquifyTargetPart.setStartLiquefy(true); - } - - // 设置液化模式光标 - setCursor(createLiquifyCursor()); - - logger.info("进入液化模式: {}", targetMesh.getName()); - - // 通知监听器 - for (ModelClickListener listener : clickListeners) { - try { - listener.onLiquifyModeEntered(targetMesh, liquifyTargetPart); - } catch (Exception ex) { - logger.error("液化模式进入事件监听器执行出错", ex); - } - } - - repaint(); - } - - /** - * 退出液化模式 - */ - private void exitLiquifyMode() { - glContextManager.executeInGLContext(() -> { - liquifyMode = false; - if (liquifyTargetPart != null) { - liquifyTargetPart.setStartLiquefy(false); - } - liquifyTargetMesh = null; - liquifyTargetPart = null; - - // 恢复默认光标 - updateCursorForHoverState(); - - logger.info("退出液化模式"); - - // 通知监听器 - for (ModelClickListener listener : clickListeners) { - try { - listener.onLiquifyModeExited(); - } catch (Exception ex) { - logger.error("液化模式退出事件监听器执行出错", ex); - } - } - - repaint(); - }); - } - - /** - * 液化模式下的单击处理(应用液化效果) - */ - private void handleLiquifyClick() { - Point mousePos = getMousePosition(); - if (mousePos == null || liquifyTargetPart == null) return; - - logger.debug("液化模式单击: {}", mousePos); - - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(mousePos.x, mousePos.y); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - // 应用液化效果 - Vector2f brushCenter = new Vector2f(modelX, modelY); - - // 判断是否按住Ctrl键,决定是否创建顶点 - boolean createVertices = ctrlPressed; - - liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize, - liquifyBrushStrength, currentLiquifyMode, 1, createVertices); - - } catch (Exception ex) { - logger.error("应用液化效果时出错", ex); - } - }); - } - - /** - * 创建液化模式光标 - */ - private Cursor createLiquifyCursor() { - // 创建自定义液化光标(圆圈) - int size = 32; - BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = cursorImg.createGraphics(); - - // 设置抗锯齿 - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 绘制透明背景 - g2d.setColor(new Color(0, 0, 0, 0)); - g2d.fillRect(0, 0, size, size); - - // 绘制圆圈 - int center = size / 2; - int radius = (int) (liquifyBrushSize * 0.1f); // 根据画笔大小缩放光标 - - // 外圈 - g2d.setColor(Color.RED); - g2d.setStroke(new BasicStroke(2f)); - g2d.drawOval(center - radius, center - radius, radius * 2, radius * 2); - - // 内圈 - g2d.setColor(new Color(255, 100, 100, 150)); - g2d.setStroke(new BasicStroke(1f)); - g2d.drawOval(center - radius / 2, center - radius / 2, radius, radius); - - // 中心点 - g2d.setColor(Color.RED); - g2d.fillOval(center - 2, center - 2, 4, 4); - - g2d.dispose(); - - return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "LiquifyCursor"); - } - - /** - * 设置液化画笔大小 - */ - public void setLiquifyBrushSize(float size) { - this.liquifyBrushSize = Math.max(1.0f, Math.min(500.0f, size)); - if (liquifyMode) { - setCursor(createLiquifyCursor()); - } - } - - /** - * 设置液化画笔强度 - */ - public void setLiquifyBrushStrength(float strength) { - this.liquifyBrushStrength = Math.max(0.0f, Math.min(2.0f, strength)); - } - - /** - * 设置液化模式 - */ - public void setLiquifyMode(ModelPart.LiquifyMode mode) { - this.currentLiquifyMode = mode; - } - - /** - * 获取当前液化状态 - */ - public boolean isInLiquifyMode() { - return liquifyMode; - } - - /** - * 获取液化目标网格 - */ - public Mesh2D getLiquifyTargetMesh() { - return liquifyTargetMesh; - } - - /** - * 获取当前二级顶点模式状态 - */ - public boolean isInSecondaryVertexMode() { - return secondaryVertexMode; - } - - /** - * 获取二级顶点目标网格 - */ - public Mesh2D getSecondaryVertexTargetMesh() { - return secondaryVertexTargetMesh; } /** * 处理单单击事件 */ private void handleSingleClick() { - if (secondaryVertexMode) { - // 二级顶点模式下单击无特殊操作 - return; - } - - if (liquifyMode) { - handleLiquifyClick(); - } - } - - // ============ 新增:操作历史记录方法 ============ - - /** - * 记录位置变化操作 - */ - private void recordPositionChange(ModelPart part, Vector2f oldPosition, Vector2f newPosition) { - if (operationHistory != null && part != null) { - operationHistory.recordOperation("SET_POSITION", part, oldPosition, newPosition); - } - } - - /** - * 记录缩放变化操作 - */ - private void recordScaleChange(ModelPart part, Vector2f oldScale, Vector2f newScale) { - if (operationHistory != null && part != null) { - operationHistory.recordOperation("SET_SCALE", part, oldScale, newScale); - } - } - - /** - * 记录旋转变化操作 - */ - private void recordRotationChange(ModelPart part, float oldRotation, float newRotation) { - if (operationHistory != null && part != null) { - operationHistory.recordOperation("SET_ROTATION", part, oldRotation, newRotation); - } - } - - /** - * 记录中心点变化操作 - */ - private void recordPivotChange(ModelPart part, Vector2f oldPivot, Vector2f newPivot) { - if (operationHistory != null && part != null) { - operationHistory.recordOperation("SET_PIVOT", part, oldPivot, newPivot); - } - } - - /** - * 记录拖拽结束操作 - */ - private void recordDragEnd(List parts, Map startPositions) { - if (operationHistory != null && parts != null && !parts.isEmpty()) { - List params = new ArrayList<>(); - params.add(parts); - params.add(startPositions); - // 添加当前位置 - for (ModelPart part : parts) { - params.add(part.getPosition()); - } - operationHistory.recordOperation("DRAG_PART_END", params.toArray()); - } - } - - /** - * 记录调整大小结束操作 - */ - private void recordResizeEnd(List parts, Map startScales) { - if (operationHistory != null && parts != null && !parts.isEmpty()) { - List params = new ArrayList<>(); - params.add(parts); - params.add(startScales); - // 添加当前缩放 - for (ModelPart part : parts) { - params.add(part.getScale()); - } - operationHistory.recordOperation("RESIZE_PART_END", params.toArray()); - } - } - - /** - * 记录旋转结束操作 - */ - private void recordRotateEnd(List parts, Map startRotations) { - if (operationHistory != null && parts != null && !parts.isEmpty()) { - List params = new ArrayList<>(); - params.add(parts); - params.add(startRotations); - // 添加当前旋转 - for (ModelPart part : parts) { - params.add(part.getRotation()); - } - operationHistory.recordOperation("ROTATE_PART_END", params.toArray()); - } - } - - /** - * 记录移动中心点结束操作 - */ - private void recordMovePivotEnd(List parts, Map startPivots) { - if (operationHistory != null && parts != null && !parts.isEmpty()) { - List params = new ArrayList<>(); - params.add(parts); - params.add(startPivots); - // 添加当前中心点 - for (ModelPart part : parts) { - params.add(part.getPivot()); - } - operationHistory.recordOperation("MOVE_PIVOT_END", params.toArray()); - } - } - - /** - * 撤回操作 - */ - public void undo() { - if (operationHistory != null && operationHistory.canUndo()) { - glContextManager.executeInGLContext(() -> { - boolean success = operationHistory.undo(); - if (success) { - repaint(); - System.out.println("撤回: " + operationHistory.getUndoDescription()); - } - }); - } else { - System.out.println("没有可撤回的操作"); - } - } - - /** - * 重做操作 - */ - public void redo() { - if (operationHistory != null && operationHistory.canRedo()) { - glContextManager.executeInGLContext(() -> { - boolean success = operationHistory.redo(); - if (success) { - repaint(); - System.out.println("重做: " + operationHistory.getRedoDescription()); - } - }); - } else { - System.out.println("没有可重做的操作"); - } - } - - /** - * 清除操作历史 - */ - public void clearHistory() { - if (operationHistory != null) { - operationHistory.clearHistory(); - System.out.println("操作历史已清除"); - } + // 单单击逻辑已迁移到各个工具中 } /** @@ -1283,86 +165,43 @@ public class ModelRenderPanel extends JPanel { clickListeners.remove(listener); } + /** + * 获取当前选中的模型部件 + * @return 选中的模型部件列表 + */ + public List getSelectedParts() { + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool instanceof SelectionTool) { + return ((SelectionTool) currentTool).getSelectedParts(); + } + return java.util.Collections.emptyList(); + } + /** * 获取当前选中的网格 */ public Mesh2D getSelectedMesh() { - return selectedMeshes.isEmpty() ? null : selectedMeshes.iterator().next(); + // 委托给工具管理系统的当前工具 + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool instanceof SelectionTool) { + return ((SelectionTool) currentTool).getSelectedMesh(); + } else if (toolManagement.getDefaultTool() instanceof SelectionTool selectedMesh) { + return selectedMesh.getSelectedMesh(); + } + return null; } /** * 获取当前选中的所有网格 */ - public Set getSelectedMeshes() { - return Collections.unmodifiableSet(selectedMeshes); - } - - /** - * 设置选中的网格(单选) - */ - public void setSelectedMesh(Mesh2D mesh) { - // 清除之前选中的所有网格 - for (Mesh2D selectedMesh : selectedMeshes) { - selectedMesh.setSelected(false); - // 清除多选列表 - selectedMesh.clearMultiSelection(); + public java.util.Set getSelectedMeshes() { + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool instanceof SelectionTool) { + return ((SelectionTool) currentTool).getSelectedMeshes(); + } else if (toolManagement.getDefaultTool() instanceof SelectionTool selectedMeshes) { + return selectedMeshes.getSelectedMeshes(); } - selectedMeshes.clear(); - - // 设置新的选中网格 - if (mesh != null) { - mesh.setSelected(true); - selectedMeshes.add(mesh); - lastSelectedMesh = mesh; // 更新最后选中的网格 - - // 通知其他选中网格添加到多选列表 - updateMultiSelectionInMeshes(); - } else { - lastSelectedMesh = null; - } - - logger.debug("设置选中网格: {}, 当前选中数量: {}", - mesh != null ? mesh.getName() : "null", selectedMeshes.size()); - } - - /** - * 添加选中的网格(多选) - */ - public void addSelectedMesh(Mesh2D mesh) { - glContextManager.executeInGLContext(() -> { - if (mesh != null && !selectedMeshes.contains(mesh)) { - mesh.setSelected(true); - selectedMeshes.add(mesh); - lastSelectedMesh = mesh; // 更新最后选中的网格 - - // 更新所有选中网格的多选列表 - updateMultiSelectionInMeshes(); - - logger.debug("添加选中网格: {}, 当前选中数量: {}", mesh.getName(), selectedMeshes.size()); - } - }); - } - - /** - * 移除选中的网格 - */ - public void removeSelectedMesh(Mesh2D mesh) { - glContextManager.executeInGLContext(() -> { - if (mesh != null && selectedMeshes.contains(mesh)) { - mesh.setSelected(false); - selectedMeshes.remove(mesh); - - // 更新所有选中网格的多选列表 - updateMultiSelectionInMeshes(); - - // 如果移除的是最后选中的网格,更新lastSelectedMesh - if (mesh == lastSelectedMesh) { - lastSelectedMesh = selectedMeshes.isEmpty() ? null : selectedMeshes.iterator().next(); - } - - logger.debug("移除选中网格: {}, 当前选中数量: {}", mesh.getName(), selectedMeshes.size()); - } - }); + return java.util.Collections.emptySet(); } /** @@ -1370,28 +209,14 @@ public class ModelRenderPanel extends JPanel { */ public void clearSelectedMeshes() { glContextManager.executeInGLContext(() -> { - // 如果当前在液化模式,先退出液化模式 - if (liquifyMode) { - exitLiquifyMode(); + // 委托给工具管理系统的当前工具 + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool instanceof SelectionTool) { + ((SelectionTool) currentTool).clearSelectedMeshes(); + } else { + toolManagement.switchToDefaultTool(); } - if (puppetMode) { - exitPuppetMode(); - } - - // 如果当前在二级顶点模式,先退出二级顶点模式 - if (secondaryVertexMode) { - exitSecondaryVertexMode(); - } - - for (Mesh2D mesh : selectedMeshes) { - mesh.setSelected(false); - mesh.setSuspension(false); - mesh.clearMultiSelection(); - } - selectedMeshes.clear(); - lastSelectedMesh = null; - logger.debug("清空所有选中网格"); }); } @@ -1401,105 +226,15 @@ public class ModelRenderPanel extends JPanel { */ public void selectAllMeshes() { glContextManager.executeInGLContext(() -> { - Model2D model = modelRef.get(); - if (model == null) return; - - // 清除之前的选择 - for (Mesh2D mesh : selectedMeshes) { - mesh.setSelected(false); - mesh.clearMultiSelection(); + // 委托给工具管理系统的当前工具 + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool instanceof SelectionTool) { + ((SelectionTool) currentTool).selectAllMeshes(); + logger.info("已全选网格"); } - selectedMeshes.clear(); - - // 获取所有网格并选中 - List allMeshes = getAllMeshesFromModel(model); - for (Mesh2D mesh : allMeshes) { - if (mesh.isVisible()) { - mesh.setSelected(true); - selectedMeshes.add(mesh); - } - } - - // 设置最后选中的网格 - if (!selectedMeshes.isEmpty()) { - lastSelectedMesh = selectedMeshes.iterator().next(); - } - - // 更新所有选中网格的多选列表 - updateMultiSelectionInMeshes(); - - logger.info("已全选 {} 个网格", selectedMeshes.size()); }); } - /** - * 更新所有选中网格的多选列表 - */ - private void updateMultiSelectionInMeshes() { - if (selectedMeshes.size() <= 1) { - // 单选或没有选中,清除所有多选列表 - for (Mesh2D mesh : getAllMeshesFromModel(modelRef.get())) { - mesh.clearMultiSelection(); - } - return; - } - - // 多选状态,更新每个选中网格的多选列表 - for (Mesh2D selectedMesh : selectedMeshes) { - selectedMesh.clearMultiSelection(); - for (Mesh2D otherMesh : selectedMeshes) { - if (otherMesh != selectedMesh) { - selectedMesh.addToMultiSelection(otherMesh); - } - } - } - } - - /** - * 获取模型中的所有网格 - */ - private List getAllMeshesFromModel(Model2D model) { - List allMeshes = new ArrayList<>(); - if (model == null) return allMeshes; - - try { - List parts = model.getParts(); - if (parts == null) return allMeshes; - - for (ModelPart part : parts) { - if (part != null && part.isVisible()) { - addMeshesFromPart(part, allMeshes); - } - } - } catch (Exception e) { - logger.error("获取模型网格时出错", e); - } - - return allMeshes; - } - - /** - * 递归从部件中获取所有网格 - */ - private void addMeshesFromPart(ModelPart part, List meshList) { - if (part == null) return; - - // 添加当前部件的网格 - List meshes = part.getMeshes(); - if (meshes != null) { - for (Mesh2D mesh : meshes) { - if (mesh != null && mesh.isVisible()) { - meshList.add(mesh); - } - } - } - - // 递归处理子部件 - for (ModelPart child : part.getChildren()) { - addMeshesFromPart(child, meshList); - } - } - /** * 获取当前选中的部件 */ @@ -1508,25 +243,62 @@ public class ModelRenderPanel extends JPanel { return selectedMesh != null ? findPartByMesh(selectedMesh) : null; } - /** - * 获取所有选中的部件 - */ - public List getSelectedParts() { - List selectedParts = new ArrayList<>(); - for (Mesh2D mesh : selectedMeshes) { - ModelPart part = findPartByMesh(mesh); - if (part != null && !selectedParts.contains(part)) { - selectedParts.add(part); - } - } - return selectedParts; - } - /** * 获取鼠标悬停的网格 */ public Mesh2D getHoveredMesh() { - return hoveredMesh; + // 委托给工具管理系统的当前工具 + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool instanceof SelectionTool) { + return ((SelectionTool) currentTool).getHoveredMesh(); + } + return null; + } + + /** + * 获取液化工具实例 + */ + public LiquifyTool getLiquifyTool() { + Tool tool = toolManagement.getTool("液化工具"); + return tool instanceof LiquifyTool ? (LiquifyTool) tool : null; + } + + /** + * 设置液化画笔大小 + */ + public void setLiquifyBrushSize(float size) { + LiquifyTool liquifyTool = getLiquifyTool(); + if (liquifyTool != null) { + liquifyTool.setLiquifyBrushSize(size); + } + } + + /** + * 设置液化画笔强度 + */ + public void setLiquifyBrushStrength(float strength) { + LiquifyTool liquifyTool = getLiquifyTool(); + if (liquifyTool != null) { + liquifyTool.setLiquifyBrushStrength(strength); + } + } + + /** + * 设置液化模式 + */ + public void setLiquifyMode(ModelPart.LiquifyMode mode) { + LiquifyTool liquifyTool = getLiquifyTool(); + if (liquifyTool != null) { + liquifyTool.setLiquifyMode(mode); + } + } + + /** + * 获取当前液化状态 + */ + public boolean isInLiquifyMode() { + Tool currentTool = toolManagement.getCurrentTool(); + return currentTool instanceof LiquifyTool; } private void initialize() { @@ -1534,7 +306,7 @@ public class ModelRenderPanel extends JPanel { setPreferredSize(new Dimension(glContextManager.getWidth(), glContextManager.getHeight())); // 添加鼠标监听器 - addMouseListeners(); + mouseManagement.addMouseListeners(); // 创建渲染线程 glContextManager.startRendering(); @@ -1554,198 +326,9 @@ public class ModelRenderPanel extends JPanel { } /** - * 添加鼠标事件监听器 + * 处理鼠标按下事件 */ - private void addMouseListeners() { - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - handleMouseClick(e); - } - - @Override - public void mousePressed(MouseEvent e) { - handleMousePressed(e); - } - - @Override - public void mouseReleased(MouseEvent e) { - handleMouseReleased(e); - } - - @Override - public void mouseExited(MouseEvent e) { - // 鼠标离开面板时恢复默认光标 - setCursor(Cursor.getDefaultCursor()); - } - }); - - addMouseWheelListener(new MouseWheelListener() { - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - if (!glContextManager.isContextInitialized()) return; - - final int screenX = e.getX(); - final int screenY = e.getY(); - final int notches = e.getWheelRotation(); - final boolean fine = e.isShiftDown(); - - glContextManager.executeInGLContext(() -> { - Camera camera = ModelRender.getCamera(); - float oldZoom = camera.getZoom(); - - // 1. 获取缩放前的世界坐标 - float[] worldPosBefore = screenToModelCoordinates(screenX, screenY); - if (worldPosBefore == null) return; - - // 2. 计算新缩放级别 - // 使用 CAMERA_ZOOM_STEP 或 ZOOM_STEP,这里沿用原有的 ZOOM_STEP 逻辑 - double step = fine ? Math.pow(ZOOM_STEP, 0.25) : ZOOM_STEP; - float newZoom = oldZoom; - if (notches > 0) { // 滚轮向下,缩小 - newZoom /= Math.pow(step, notches); - } else { // 滚轮向上,放大 - newZoom *= Math.pow(step, -notches); - } - // 限制范围,使用 CAMERA_ZOOM_MIN/MAX 或 ZOOM_MIN/MAX,这里沿用原有的 ZOOM_MIN/MAX - newZoom = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, newZoom)); - - if (Math.abs(newZoom - oldZoom) < 1e-6f) { - return; // 缩放级别无变化 - } - - // 3. 应用新缩放并获取缩放后的世界坐标 - camera.setZoom(newZoom); - float[] worldPosAfter = screenToModelCoordinates(screenX, screenY); - if (worldPosAfter == null) { - camera.setZoom(oldZoom); // 如果计算失败则恢复 - return; - } - - // 4. 计算相机需要平移的量,以保持鼠标指针下的点不变 - float panX = worldPosBefore[0] - worldPosAfter[0]; - float panY = worldPosBefore[1] - worldPosAfter[1]; - - // 5. 应用平移 - camera.move(panX, panY); - - // 6. 更新面板的缩放状态变量,禁用平滑缩放以确保一致性 - glContextManager.setDisplayScale(newZoom); - glContextManager.setTargetScale(newZoom); - }); - } - }); - - addMouseMotionListener(new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - handleMouseMove(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - handleMouseDragged(e); - } - }); - - // 新增:键盘监听器用于检测Shift和Ctrl键 - addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_SHIFT) { - shiftPressed = true; - } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { - ctrlPressed = true; - if (liquifyMode && liquifyTargetMesh != null) { - liquifyTargetMesh.setRenderVertices(true); - logger.debug("液化模式下按住Ctrl,开启顶点渲染"); - } - } else if (e.getKeyCode() == KeyEvent.VK_A && ctrlPressed) { - // Ctrl+A 全选 - e.consume(); // 阻止默认行为 - selectAllMeshes(); - } - } - - @Override - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_SHIFT) { - shiftPressed = false; - } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { - ctrlPressed = false; - if (liquifyMode && liquifyTargetMesh != null) { - liquifyTargetMesh.setRenderVertices(false); - logger.debug("液化模式下松开Ctrl,关闭顶点渲染"); - } - } - } - }); - - addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_SHIFT) { - shiftPressed = true; - } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { - ctrlPressed = true; - } else if (e.getKeyCode() == KeyEvent.VK_A && ctrlPressed) { - // Ctrl+A 全选 - e.consume(); // 阻止默认行为 - selectAllMeshes(); - } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - // ESC 键取消所有选择 - e.consume(); - clearSelectedMeshes(); - logger.info("按ESC键取消所有选择"); - } else if (e.getKeyCode() == KeyEvent.VK_D && ctrlPressed) { - // Ctrl+D 取消选择(可选,保留作为备选方案) - e.consume(); - clearSelectedMeshes(); - logger.info("按Ctrl+D取消所有选择"); - } - } - - @Override - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_SHIFT) { - shiftPressed = false; - } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { - ctrlPressed = false; - } - } - }); - - addMouseWheelListener(new MouseWheelListener() { - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - // 如果没有初始化上下文也允许修改 targetScale(视觉效果会在渲染线程平滑完成) - int notches = e.getWheelRotation(); // 向后为正 - boolean fine = (e.isShiftDown() || shiftPressed); // 支持 Shift 更精细控制 - - // 指数步长:每格滚轮缩放 ZOOM_STEP 的幂 - double step = fine ? Math.pow(ZOOM_STEP, 0.25) : ZOOM_STEP; - if (notches > 0) { - // 滚轮下:缩小 - glContextManager.targetScale *= Math.pow(1.0 / step, notches); - } else if (notches < 0) { - // 滚轮上:放大 - glContextManager.targetScale *= Math.pow(step, -notches); - } - - // 限制范围 - glContextManager.targetScale = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, glContextManager.targetScale)); - } - }); - - // 确保面板可以获得焦点以接收键盘事件 - setFocusable(true); - requestFocusInWindow(); - } - - /** - * 处理鼠标按下事件(开始拖拽或调整大小) - */ - private void handleMousePressed(MouseEvent e) { + public void handleMousePressed(MouseEvent e) { if (!glContextManager.isContextInitialized()) return; final int screenX = e.getX(); final int screenY = e.getY(); @@ -1754,514 +337,53 @@ public class ModelRenderPanel extends JPanel { // 首先处理中键拖拽(摄像机控制),在任何模式下都可用 if (SwingUtilities.isMiddleMouseButton(e)) { glContextManager.setCameraDragging(true); - lastCameraDragX = screenX; - lastCameraDragY = screenY; - - // 记录摄像机起始位置 - Camera camera = ModelRender.getCamera(); - cameraStartX = camera.getPosition().x; - cameraStartY = camera.getPosition().y; - - // 设置拖拽光标 + cameraManagement.setLastCameraDragX(screenX); + cameraManagement.setLastCameraDragY(screenY); setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); return; } - // 二级顶点模式下的左键处理 - if (secondaryVertexMode && SwingUtilities.isLeftMouseButton(e)) { - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; + float[] modelCoords = worldManagement.screenToModelCoordinates(screenX, screenY); - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - handleSecondaryVertexMousePressed(modelX, modelY); - } catch (Exception ex) { - logger.error("处理二级顶点鼠标按下时出错", ex); - } - }); - return; - } - - if (puppetMode && SwingUtilities.isLeftMouseButton(e)) { - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - handlePuppetMousePressed(modelX, modelY); - } catch (Exception ex) { - logger.error("处理木偶工具鼠标按下时出错", ex); - } - }); - return; - } - - // 液化模式下的左键处理 - if (liquifyMode && SwingUtilities.isLeftMouseButton(e)) { - // 在液化模式下,左键按下直接开始液化操作 - currentDragMode = DragMode.NONE; // 液化模式使用特殊拖拽逻辑 - logger.debug("液化模式下开始拖拽"); - - // 立即应用一次液化效果 - handleLiquifyClick(); - return; - } - - // 非液化模式的正常处理 - if (liquifyMode) { - // 液化模式下只处理左键和中键,其他按钮忽略 - return; - } - - shiftDuringDrag = e.isShiftDown(); - - glContextManager.executeInGLContext(() -> { - try { - // 转换屏幕坐标到模型坐标 - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - // 首先检查是否点击了选择框的调整手柄 - // 多选时只对最后一个选中的网格进行操作 - Mesh2D targetMeshForHandle = selectedMeshes.isEmpty() ? null : lastSelectedMesh; - DragMode dragMode = targetMeshForHandle != null ? - checkResizeHandleHit(modelX, modelY, targetMeshForHandle) : DragMode.NONE; - - // 清空之前的状态记录 - dragStartPositions.clear(); - dragStartScales.clear(); - dragStartRotations.clear(); - dragStartPivots.clear(); - - if (dragMode == DragMode.ROTATE) { - // 开始旋转 - 记录初始旋转状态 - List selectedParts = getSelectedParts(); - for (ModelPart part : selectedParts) { - dragStartRotations.put(part, part.getRotation()); - } - currentDragMode = DragMode.ROTATE; - dragStartX = modelX; - dragStartY = modelY; - - // 获取边界框和中心点 - BoundingBox bounds = targetMeshForHandle.getBounds(); - rotationCenter.set((bounds.getMinX() + bounds.getMaxX()) / 2.0f, - (bounds.getMinY() + bounds.getMaxY()) / 2.0f); - - // 计算初始角度 - rotationStartAngle = (float) Math.atan2(dragStartY - rotationCenter.y, - dragStartX - rotationCenter.x); - - } else if (dragMode == DragMode.MOVE_PIVOT && targetMeshForHandle != null) { - // 开始移动中心点 - 记录初始中心点状态 - List selectedParts = getSelectedParts(); - for (ModelPart part : selectedParts) { - dragStartPivots.put(part, new Vector2f(part.getPivot())); - } - currentDragMode = DragMode.MOVE_PIVOT; - dragStartX = modelX; - dragStartY = modelY; - - // 记录初始中心点位置 - BoundingBox bounds = targetMeshForHandle.getBounds(); - rotationCenter.set((bounds.getMinX() + bounds.getMaxX()) / 2.0f, - (bounds.getMinY() + bounds.getMaxY()) / 2.0f); - } else if (dragMode != DragMode.NONE && targetMeshForHandle != null) { - // 开始调整大小 - 记录初始缩放状态 - List selectedParts = getSelectedParts(); - for (ModelPart part : selectedParts) { - dragStartScales.put(part, new Vector2f(part.getScale())); - } - currentDragMode = dragMode; - dragStartX = modelX; // 记录拖拽起始位置 - dragStartY = modelY; - - BoundingBox bounds = targetMeshForHandle.getBounds(); - resizeStartWidth = bounds.getWidth(); - resizeStartHeight = bounds.getHeight(); - - } else { - // 检查是否点击了网格(移动操作) - Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY); - - if (clickedMesh != null) { - handleMultiSelect(clickedMesh, e.isShiftDown(), e.isControlDown()); - - // 记录初始位置状态 - List selectedParts = getSelectedParts(); - - for (ModelPart part : selectedParts) { - dragStartPositions.put(part, new Vector2f(part.getPosition())); - } - - // 设置拖拽目标(如果点击了已选中的网格) - draggedPart = findPartByMesh(clickedMesh); - dragStartX = modelX; - dragStartY = modelY; - currentDragMode = DragMode.MOVE; - logger.debug("开始移动网格: {}", clickedMesh.getName()); - } else { - // 点击空白区域 - if (e.isControlDown() || e.isShiftDown()) { - logger.info("按住Ctrl/Shift点击空白区域,保持当前选择状态 - Ctrl: {}, Shift: {}, 当前选中数量: {}", - e.isControlDown(), e.isShiftDown(), selectedMeshes.size()); - currentDragMode = DragMode.NONE; - } - } - } - - // 更新拖拽过程中的光标 - updateCursorForDragMode(currentDragMode); - - } catch (Exception ex) { - logger.error("处理鼠标按下时出错", ex); - } - }); - } - - /** - * 根据拖拽模式更新光标 - */ - private void updateCursorForDragMode(DragMode dragMode) { - Cursor newCursor = getCursorForDragMode(dragMode); - if (!newCursor.equals(currentCursor)) { - currentCursor = newCursor; - SwingUtilities.invokeLater(() -> setCursor(newCursor)); + // 如果有激活的工具,优先交给工具处理 + if (toolManagement.hasActiveTool() && modelCoords != null) { + glContextManager.executeInGLContext(() -> toolManagement.handleMousePressed(e, modelCoords[0], modelCoords[1])); } } - /** - * 根据拖拽模式获取对应的光标 - */ - private Cursor getCursorForDragMode(DragMode dragMode) { - switch (dragMode) { - case MOVE: - return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); - case RESIZE_LEFT: - case RESIZE_RIGHT: - return Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); - case RESIZE_TOP: - case RESIZE_BOTTOM: - return Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); - case RESIZE_TOP_LEFT: - case RESIZE_BOTTOM_RIGHT: - return Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR); - case RESIZE_TOP_RIGHT: - case RESIZE_BOTTOM_LEFT: - return Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR); - case ROTATE: - // 使用手型光标表示旋转 - return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); - case MOVE_PIVOT: - // 使用十字光标表示移动中心点 - return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); - case MOVE_SECONDARY_VERTEX: - // 移动二级顶点时使用手型光标 - return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); - case MOVE_PUPPET_PIN: - // 移动木偶控制点时使用手型光标 - return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); - case NONE: - default: - return Cursor.getDefaultCursor(); - } + public ToolManagement getToolManagement() { + return toolManagement; + } + + public void switchTool(String toolName) { + glContextManager.executeInGLContext(() -> toolManagement.switchTool(toolName)); } /** - * 处理多选逻辑 + * 切换到液化工具 */ - private void handleMultiSelect(Mesh2D clickedMesh, boolean isShiftDown, boolean isCtrlDown) { - if (isCtrlDown) { - if (selectedMeshes.contains(clickedMesh)) { - removeSelectedMesh(clickedMesh); - } else { - addSelectedMesh(clickedMesh); - } - } else if (isShiftDown && lastSelectedMesh != null) { - selectRange(lastSelectedMesh, clickedMesh); - } else if (!isInMultiSelection()) { - if (!selectedMeshes.contains(clickedMesh)) { - setSelectedMesh(clickedMesh); - } - } - - logger.debug("多选处理完成 - Ctrl: {}, Shift: {}, 选中数量: {}", - isCtrlDown, isShiftDown, selectedMeshes.size()); + public void switchToLiquifyTool() { + switchTool("液化工具"); } - /** - * 检查是否处于多选状态 - */ - private boolean isInMultiSelection() { - return selectedMeshes.size() > 1; - } - - /** - * 选择两个网格之间的所有网格 - */ - private void selectRange(Mesh2D fromMesh, Mesh2D toMesh) { - Model2D model = modelRef.get(); - if (model == null) return; - - List allMeshes = getAllMeshesFromModel(model); - int fromIndex = allMeshes.indexOf(fromMesh); - int toIndex = allMeshes.indexOf(toMesh); - - if (fromIndex == -1 || toIndex == -1) { - // 如果找不到网格,回退到单选 - setSelectedMesh(toMesh); - return; - } - - // 确定选择范围 - int start = Math.min(fromIndex, toIndex); - int end = Math.max(fromIndex, toIndex); - - // 先清除当前选择 - //clearSelectedMeshes(); - - // 添加范围内的所有网格到选中集合 - for (int i = start; i <= end; i++) { - Mesh2D mesh = allMeshes.get(i); - if (mesh.isVisible()) { - mesh.setSelected(true); - selectedMeshes.add(mesh); - } - } - - // 更新最后选中的网格和多选列表 - lastSelectedMesh = toMesh; - updateMultiSelectionInMeshes(); - - logger.debug("范围选择: 从 {} 到 {}, 选中 {} 个网格", - fromMesh.getName(), toMesh.getName(), selectedMeshes.size()); - } - - /** - * 将 2D 向量 (Vector2f) 乘以 3x3 矩阵 (Matrix3f)。 - * - * @param v 要变换的向量 (局部坐标) - * @param m 变换矩阵 (世界变换) - * @return 变换后的新向量 (世界坐标) - */ - private Vector2f transformVector2f(Vector2f v, Matrix3f m) { - float x = v.x; - float y = v.y; - - // 计算新的 x' 和 y' - // x' = m00*x + m01*y + m02 - // y' = m10*x + m11*y + m12 - float newX = m.m00() * x + m.m01() * y + m.m02(); - float newY = m.m10() * x + m.m11() * y + m.m12(); - - // 直接修改原向量并返回 - return v.set(newX, newY); - } - - /** - * 计算指定网格在世界坐标系下的边界框。 - */ - private BoundingBox getWorldBounds(Mesh2D mesh) { - ModelPart part = findPartByMesh(mesh); - if (part == null) { - mesh.updateBounds(); // 至少保证局部边界框是新的 - return mesh.getBounds(); - } - - // 确保 ModelPart 的世界变换是最新的 - Matrix3f worldTransform = part.getWorldTransform(); - - // 获取局部边界框 - mesh.updateBounds(); - BoundingBox localBounds = mesh.getBounds(); - - // 转换四个角到世界坐标 - Vector2f min = new Vector2f(localBounds.getMinX(), localBounds.getMinY()); - Vector2f max = new Vector2f(localBounds.getMaxX(), localBounds.getMaxY()); - Vector2f p1 = new Vector2f(min.x, max.y); // 左上 - Vector2f p2 = new Vector2f(max.x, min.y); // 右下 - - // 应用世界变换:使用手动实现的 transformVector2f 方法 - transformVector2f(min, worldTransform); - transformVector2f(max, worldTransform); - transformVector2f(p1, worldTransform); - transformVector2f(p2, worldTransform); - - // 创建新的世界边界框并扩展 - BoundingBox worldBounds = new BoundingBox(); - worldBounds.expand(min.x, min.y); - worldBounds.expand(max.x, max.y); - worldBounds.expand(p1.x, p1.y); - worldBounds.expand(p2.x, p2.y); - - return worldBounds; - } - - /** - * 检查是否点击了选择框的调整手柄 - */ - private DragMode checkResizeHandleHit(float modelX, float modelY, Mesh2D targetMesh) { - if (targetMesh == null) { - return DragMode.NONE; - } - - BoundingBox bounds; - Vector2f center; - - // 多选状态下使用多选边界框 - if (targetMesh.isInMultiSelection()) { - bounds = targetMesh.getMultiSelectionBounds(); - center = bounds.getCenter(); - } else { - bounds = targetMesh.getBounds(); - center = targetMesh.getPivot(); - } - - // 获取摄像机偏移 - Vector2f camOffset = ModelRender.getCameraOffset(); - - // 应用偏移,将 model 坐标转换到相对于摄像机的坐标系 - float checkX = modelX; - float checkY = modelY; - - // 将中心点也转换到相同坐标系 - //center = new Vector2f(center).sub(camOffset); - - float scaleFactor = calculateScaleFactor(); - float borderThickness = BORDER_THICKNESS / scaleFactor; - float cornerSize = CORNER_SIZE / scaleFactor; - - float minX = bounds.getMinX(); - float minY = bounds.getMinY(); - float maxX = bounds.getMaxX(); - float maxY = bounds.getMaxY(); - - DragMode result = DragMode.NONE; - - // 检查中心点 - if (isPointInCenterHandle(checkX, checkY, center.x, center.y, cornerSize)) { - result = DragMode.MOVE_PIVOT; - } - - // 检查旋转手柄 - if (result == DragMode.NONE && isPointInRotationHandle(checkX, checkY, center.x, center.y, minY, cornerSize)) { - result = DragMode.ROTATE; - } - - // 扩展边界以包含调整手柄区域 - float expandedMinX = minX - borderThickness; - float expandedMinY = minY - borderThickness; - float expandedMaxX = maxX + borderThickness; - float expandedMaxY = maxY + borderThickness; - - if (result == DragMode.NONE) { - if (checkX < expandedMinX || checkX > expandedMaxX || - checkY < expandedMinY || checkY > expandedMaxY) { - return DragMode.NONE; - } - } - - // 检查角点 - if (result == DragMode.NONE && isPointInCorner(checkX, checkY, minX, minY, cornerSize)) { - result = DragMode.RESIZE_TOP_LEFT; - } - if (result == DragMode.NONE && isPointInCorner(checkX, checkY, maxX, minY, cornerSize)) { - result = DragMode.RESIZE_TOP_RIGHT; - } - if (result == DragMode.NONE && isPointInCorner(checkX, checkY, minX, maxY, cornerSize)) { - result = DragMode.RESIZE_BOTTOM_LEFT; - } - if (result == DragMode.NONE && isPointInCorner(checkX, checkY, maxX, maxY, cornerSize)) { - result = DragMode.RESIZE_BOTTOM_RIGHT; - } - - // 检查边 - if (result == DragMode.NONE) { - if (checkX >= minX - borderThickness && checkX <= minX + borderThickness) { - result = DragMode.RESIZE_LEFT; - } else if (checkX >= maxX - borderThickness && checkX <= maxX + borderThickness) { - result = DragMode.RESIZE_RIGHT; - } else if (checkY >= minY - borderThickness && checkY <= minY + borderThickness) { - result = DragMode.RESIZE_TOP; - } else if (checkY >= maxY - borderThickness && checkY <= maxY + borderThickness) { - result = DragMode.RESIZE_BOTTOM; - } - } - - return result; - } - - /** - * 检查点是否在中心点区域内 - */ - private boolean isPointInCenterHandle(float x, float y, float centerX, float centerY, float handleSize) { - return Math.abs(x - centerX) <= handleSize && Math.abs(y - centerY) <= handleSize; - } - - /** - * 检查点是否在旋转手柄区域内 - */ - private boolean isPointInRotationHandle(float x, float y, float centerX, float centerY, float topY, float handleSize) { - // 旋转手柄位于边界框上方一定距离 - float rotationHandleY = topY - ROTATION_HANDLE_DISTANCE / calculateScaleFactor(); - float rotationHandleX = centerX; - - return Math.abs(x - rotationHandleX) <= handleSize && Math.abs(y - rotationHandleY) <= handleSize; - } - - /** - * 计算当前缩放因子(模型单位与屏幕像素的比例) - */ - private float calculateScaleFactor() { - int panelWidth = getWidth(); - int panelHeight = getHeight(); - - if (panelWidth <= 0 || panelHeight <= 0 || glContextManager.getHeight() <= 0 || glContextManager.getHeight() <= 0) { - return 1.0f; - } - - // 计算面板与离屏缓冲区的比例 - float scaleX = (float) panelWidth / glContextManager.getWidth(); - float scaleY = (float) panelHeight / glContextManager.getHeight(); - - // 基本面板缩放(保持与现有逻辑一致) - float base = Math.min(scaleX, scaleY); - - // 乘以平滑的 displayScale,使视觉上缩放与检测区域一致 - return base * glContextManager.displayScale; - } - - /** - * 检查点是否在角点区域内 - */ - private boolean isPointInCorner(float x, float y, float cornerX, float cornerY, float cornerSize) { - return x >= cornerX - cornerSize && x <= cornerX + cornerSize && - y >= cornerY - cornerSize && y <= cornerY + cornerSize; + public Tool getCurrentTool() { + return toolManagement.getCurrentTool(); } /** * 处理鼠标拖拽事件 */ - private void handleMouseDragged(MouseEvent e) { + public void handleMouseDragged(MouseEvent e) { if (glContextManager.isCameraDragging()) { final int screenX = e.getX(); final int screenY = e.getY(); // 计算鼠标移动距离 - final int deltaX = screenX - lastCameraDragX; - final int deltaY = screenY - lastCameraDragY; + final int deltaX = screenX - cameraManagement.getLastCameraDragX(); + final int deltaY = screenY - cameraManagement.getLastCameraDragY(); // 更新最后拖拽位置 - lastCameraDragX = screenX; - lastCameraDragY = screenY; + cameraManagement.setLastCameraDragX(screenX); + cameraManagement.setLastCameraDragY(screenY); // 确保在 GL 上下文线程中执行摄像机移动 glContextManager.executeInGLContext(() -> { @@ -2281,423 +403,227 @@ public class ModelRenderPanel extends JPanel { return; } - // 二级顶点模式下的拖拽处理 - if (secondaryVertexMode && SwingUtilities.isLeftMouseButton(e)) { - final int screenX = e.getX(); - final int screenY = e.getY(); - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; + final float[][] modelCoords = {worldManagement.screenToModelCoordinates(e.getX(), e.getY())}; - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - handleSecondaryVertexMouseDragged(modelX, modelY); - } catch (Exception ex) { - logger.error("处理二级顶点拖拽时出错", ex); - } - }); + // 如果有激活的工具,优先交给工具处理 + if (toolManagement.hasActiveTool() && modelCoords[0] != null) { + glContextManager.executeInGLContext(() -> toolManagement.handleMouseDragged(e, modelCoords[0][0], modelCoords[0][1])); return; } - - // 木偶工具模式下的拖拽处理 - if (puppetMode && SwingUtilities.isLeftMouseButton(e)) { - final int screenX = e.getX(); - final int screenY = e.getY(); - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - handlePuppetMouseDragged(modelX, modelY); - } catch (Exception ex) { - logger.error("处理木偶工具拖拽时出错", ex); - } - }); - return; - } - - // 液化模式下的拖拽处理(优先于其他模式) - if (liquifyMode && SwingUtilities.isLeftMouseButton(e)) { - handleLiquifyDrag(e); - return; - } - - // 普通模式下的拖拽处理 - if (currentDragMode == DragMode.NONE) return; - - final int screenX = e.getX(); - final int screenY = e.getY(); - glContextManager.executeInGLContext(() -> { - try { - if (currentDragMode == DragMode.NONE) { - logger.debug("拖拽已取消,跳过处理"); - return; - } - - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - switch (currentDragMode) { - case MOVE: - handleMoveDrag(modelX, modelY); - break; - case ROTATE: - handleRotateDrag(modelX, modelY); - break; - case MOVE_PIVOT: - handleMovePivotDrag(modelX, modelY); - break; - default: - handleResizeDrag(modelX, modelY); - break; - } - - } catch (Exception ex) { - logger.error("处理鼠标拖拽时出错", ex); - } - }); } /** - * 液化模式下的拖拽处理(连续应用液化效果) + * 处理鼠标释放事件 */ - private void handleLiquifyDrag(MouseEvent e) { - if (!liquifyMode || liquifyTargetPart == null) return; - - final int screenX = e.getX(); - final int screenY = e.getY(); - - glContextManager.executeInGLContext(() -> { - try { - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - // 应用液化效果 - Vector2f brushCenter = new Vector2f(modelX, modelY); - - // 判断是否按住Ctrl键,决定是否创建顶点 - boolean createVertices = ctrlPressed; - - liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize, - liquifyBrushStrength, currentLiquifyMode, 1, createVertices); - - } catch (Exception ex) { - logger.error("应用液化拖拽效果时出错", ex); - } - }); - } - - /** - * 处理移动中心点拖拽 - */ - private void handleMovePivotDrag(float modelX, float modelY) { - if (lastSelectedMesh == null) return; - - float deltaX = modelX - dragStartX; - float deltaY = modelY - dragStartY; - - // 只移动主选中网格的中心点 - ModelPart selectedPart = findPartByMesh(lastSelectedMesh); - if (selectedPart == null) return; - - Vector2f currentPivot = selectedPart.getPivot(); - float newPivotX = currentPivot.x + deltaX; - float newPivotY = currentPivot.y + deltaY; - - if (selectedPart.setPivot(newPivotX, newPivotY)) { - dragStartX = modelX; - dragStartY = modelY; - rotationCenter.set(newPivotX, newPivotY); - } - } - - /** - * 处理移动拖拽 - */ - private void handleMoveDrag(float modelX, float modelY) { - if (selectedMeshes.isEmpty()) return; - - float deltaX = modelX - dragStartX; - float deltaY = modelY - dragStartY; - - // 移动所有选中的部件 - 使用 ModelPart 的多选移动功能 - List selectedParts = getSelectedParts(); - for (ModelPart part : selectedParts) { - Vector2f pos = part.getPosition(); - part.setPosition(pos.x + deltaX, pos.y + deltaY); - - // 同步移动该部件下的所有网格的二级顶点 - syncSecondaryVerticesForPart(part, deltaX, deltaY); - } - - // 更新拖拽起始位置 - dragStartX = modelX; - dragStartY = modelY; - - // 强制更新多选边界框 - updateMultiSelectionBoundsForSelectedMeshes(); - } - - /** - * 同步部件下所有网格的二级顶点位置 - */ - private void syncSecondaryVerticesForPart(ModelPart part, float deltaX, float deltaY) { - if (part == null) return; - - // 获取部件下的所有网格 - List meshes = part.getMeshes(); - if (meshes == null) return; - - for (Mesh2D mesh : meshes) { - if (mesh != null && mesh.getSecondaryVertexCount() > 0) { - // 移动二级顶点 - mesh.moveSecondaryVertices(deltaX, deltaY); - } - } - - // 递归处理子部件 - for (ModelPart child : part.getChildren()) { - syncSecondaryVerticesForPart(child, deltaX, deltaY); - } - } - - /** - * 处理旋转拖拽 - */ - private void handleRotateDrag(float modelX, float modelY) { - if (lastSelectedMesh == null) return; - - // 计算当前角度 - float currentAngle = (float) Math.atan2(modelY - rotationCenter.y, - modelX - rotationCenter.x); - - // 计算旋转增量 - float deltaAngle = currentAngle - rotationStartAngle; - - // 如果按住Shift键,以15度为步长进行约束旋转 - if (shiftPressed || shiftDuringDrag) { - float constraintStep = (float) (Math.PI / 12); // 15度 - deltaAngle = Math.round(deltaAngle / constraintStep) * constraintStep; - } - - // 应用旋转到所有选中的部件 - 使用 ModelPart 的多选旋转功能 - List selectedParts = getSelectedParts(); - for (ModelPart part : selectedParts) { - part.rotate(deltaAngle); - } - - // 更新旋转起始角度 - rotationStartAngle = currentAngle; - - // 强制更新多选边界框 - updateMultiSelectionBoundsForSelectedMeshes(); - - logger.debug("旋转角度增量: {} 度", Math.toDegrees(deltaAngle)); - } - - /** - * 处理调整大小拖拽 - */ - private void handleResizeDrag(float modelX, float modelY) { - if (lastSelectedMesh == null) return; - - ModelPart selectedPart = findPartByMesh(lastSelectedMesh); - if (selectedPart == null) return; - - float deltaX = modelX - dragStartX; - float deltaY = modelY - dragStartY; - - float relScaleX = 1.0f; - float relScaleY = 1.0f; - - // 根据拖拽模式计算相对缩放比例 - switch (currentDragMode) { - case RESIZE_LEFT: - relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth); - break; - case RESIZE_RIGHT: - relScaleX = (resizeStartWidth + deltaX) / Math.max(1e-6f, resizeStartWidth); - break; - case RESIZE_TOP: - relScaleY = (resizeStartHeight - deltaY) / Math.max(1e-6f, resizeStartHeight); - break; - case RESIZE_BOTTOM: - relScaleY = (resizeStartHeight + deltaY) / Math.max(1e-6f, resizeStartHeight); - break; - case RESIZE_TOP_LEFT: - relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth); - relScaleY = (resizeStartHeight - deltaY) / Math.max(1e-6f, resizeStartHeight); - break; - case RESIZE_TOP_RIGHT: - relScaleX = (resizeStartWidth + deltaX) / Math.max(1e-6f, resizeStartWidth); - relScaleY = (resizeStartHeight - deltaY) / Math.max(1e-6f, resizeStartHeight); - break; - case RESIZE_BOTTOM_LEFT: - relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth); - relScaleY = (resizeStartHeight + deltaY) / Math.max(1e-6f, resizeStartHeight); - break; - case RESIZE_BOTTOM_RIGHT: - relScaleX = (resizeStartWidth + deltaX) / Math.max(1e-6f, resizeStartWidth); - relScaleY = (resizeStartHeight + deltaY) / Math.max(1e-6f, resizeStartHeight); - break; - } - - // 如果按住Shift键,等比例缩放 - if (shiftPressed || shiftDuringDrag) { - float uniform = (relScaleX + relScaleY) * 0.5f; - relScaleX = uniform; - relScaleY = uniform; - } - - // 应用缩放到所有选中的部件 - 使用 ModelPart 的多选缩放功能 - List selectedParts = getSelectedParts(); - for (ModelPart part : selectedParts) { - Vector2f currentScale = part.getScale(); - part.setScale(currentScale.x * relScaleX, currentScale.y * relScaleY); - - // 同步缩放该部件下的所有网格的二级顶点 - syncSecondaryVerticesScaleForPart(part, relScaleX, relScaleY); - } - - // 更新拖拽起始位置和初始尺寸 - dragStartX = modelX; - dragStartY = modelY; - resizeStartWidth *= relScaleX; - resizeStartHeight *= relScaleY; - - // 强制更新多选边界框 - updateMultiSelectionBoundsForSelectedMeshes(); - } - - private void syncSecondaryVerticesScaleForPart(ModelPart part, float scaleX, float scaleY) { - if (part == null) return; - - // 获取部件下的所有网格 - List meshes = part.getMeshes(); - if (meshes == null) return; - - for (Mesh2D mesh : meshes) { - if (mesh != null && mesh.getSecondaryVertexCount() > 0) { - // 同步二级顶点到新的边界 - mesh.syncSecondaryVerticesToBounds(); - } - } - - // 递归处理子部件 - for (ModelPart child : part.getChildren()) { - syncSecondaryVerticesScaleForPart(child, scaleX, scaleY); - } - } - - /** - * 更新所有选中网格的多选边界框 - */ - @Deprecated(forRemoval = true) - private void updateMultiSelectionBoundsForSelectedMeshes() { - //if (selectedMeshes.size() <= 1) return; -// - //for (Mesh2D mesh : selectedMeshes) { - // if (mesh.isInMultiSelection()) { - // mesh.updateBounds(); - // mesh.forceUpdateMultiSelectionBounds(); - // } - //} - } - - /** - * 处理鼠标释放事件(结束拖拽并记录操作历史) - */ - private void handleMouseReleased(MouseEvent e) { + public void handleMouseReleased(MouseEvent e) { // 首先处理摄像机拖拽释放 - if (glContextManager.isCameraDragging() && (SwingUtilities.isMiddleMouseButton(e) || liquifyMode)) { + if (glContextManager.isCameraDragging() && SwingUtilities.isMiddleMouseButton(e)) { glContextManager.setCameraDragging(false); // 恢复悬停状态的光标 updateCursorForHoverState(); return; } - // 二级顶点模式下的释放处理 - if (secondaryVertexMode && SwingUtilities.isLeftMouseButton(e)) { - // 二级顶点模式下不需要记录操作历史,直接重置状态 - currentDragMode = DragMode.NONE; - logger.debug("二级顶点拖拽结束"); - return; + float[] modelCoords = worldManagement.screenToModelCoordinates(e.getX(), e.getY()); + + // 如果有激活的工具,优先交给工具处理 + if (toolManagement.hasActiveTool() && modelCoords != null) { + toolManagement.handleMouseReleased(e, modelCoords[0], modelCoords[1]); } - - // 液化模式下的释放处理 - if (liquifyMode && SwingUtilities.isLeftMouseButton(e)) { - // 液化模式下不需要记录操作历史,直接重置状态 - currentDragMode = DragMode.NONE; - logger.debug("液化拖拽结束"); - return; - } - - if (currentDragMode != DragMode.NONE) { - // 记录操作历史 - try { - List selectedParts = getSelectedParts(); - switch (currentDragMode) { - case MOVE: - if (!dragStartPositions.isEmpty() && !selectedParts.isEmpty()) { - recordDragEnd(selectedParts, new HashMap<>(dragStartPositions)); - } - break; - case ROTATE: - if (!dragStartRotations.isEmpty() && !selectedParts.isEmpty()) { - recordRotateEnd(selectedParts, new HashMap<>(dragStartRotations)); - } - break; - case MOVE_PIVOT: - if (!dragStartPivots.isEmpty() && !selectedParts.isEmpty()) { - recordMovePivotEnd(selectedParts, new HashMap<>(dragStartPivots)); - } - break; - default: - if (!dragStartScales.isEmpty() && !selectedParts.isEmpty()) { - recordResizeEnd(selectedParts, new HashMap<>(dragStartScales)); - } - break; - } - } catch (Exception ex) { - logger.error("记录操作历史时出错", ex); - } - } - - // 重置状态 - isDragging = false; - draggedPart = null; - currentDragMode = DragMode.NONE; - shiftDuringDrag = false; - - // 清空状态记录 - dragStartPositions.clear(); - dragStartScales.clear(); - dragStartRotations.clear(); - dragStartPivots.clear(); - - // 恢复悬停状态的光标 - updateCursorForHoverState(); } + /** + * 处理鼠标点击事件 + */ + public void handleMouseClick(MouseEvent e) { + if (!glContextManager.isContextInitialized()) return; + + final int screenX = e.getX(); + final int screenY = e.getY(); + + long currentTime = System.currentTimeMillis(); + boolean isDoubleClick = (currentTime - lastClickTime) < DOUBLE_CLICK_INTERVAL; + lastClickTime = currentTime; + + if (isDoubleClick) { + // 取消单单击计时器 + doubleClickTimer.stop(); + handleDoubleClick(e); + } else { + float[] modelCoords = worldManagement.screenToModelCoordinates(screenX, screenY); + + // 如果有激活的工具,优先交给工具处理 + if (toolManagement.hasActiveTool() && modelCoords != null) { + toolManagement.handleMouseClicked(e, modelCoords[0], modelCoords[1]); + return; + } + + glContextManager.executeInGLContext(() -> { + try { + if (modelCoords == null) return; + + float modelX = modelCoords[0]; + float modelY = modelCoords[1]; + + logger.debug("点击位置:({}, {})", modelX, modelY); + + // 触发点击事件 + for (ModelClickListener listener : clickListeners) { + try { + listener.onModelClicked(null, modelX, modelY, screenX, screenY); + } catch (Exception ex) { + logger.error("点击事件监听器执行出错", ex); + } + } + } catch (Exception ex) { + logger.error("处理鼠标点击时出错", ex); + } + }); + doubleClickTimer.restart(); + } + } + + /** + * 处理鼠标移动事件 + */ + public void handleMouseMove(MouseEvent e) { + if (!glContextManager.isContextInitialized()) return; + + final int screenX = e.getX(); + final int screenY = e.getY(); + + if (glContextManager.isCameraDragging()) { + return; + } + + float[] modelCoords = worldManagement.screenToModelCoordinates(screenX, screenY); + + // 如果有激活的工具,优先交给工具处理 + if (toolManagement.hasActiveTool() && modelCoords != null) { + toolManagement.handleMouseMoved(e, modelCoords[0], modelCoords[1]); + return; + } + } + + /** + * 根据悬停状态更新光标(无坐标版本,用于鼠标释放后) + */ + private void updateCursorForHoverState() { + Point mousePos = getMousePosition(); + if (mousePos != null) { + float[] modelCoords = worldManagement.screenToModelCoordinates(mousePos.x, mousePos.y); + if (modelCoords != null) { + // 委托给工具管理系统的当前工具 + Tool currentTool = toolManagement.getCurrentTool(); + if (currentTool != null) { + setCursor(currentTool.getToolCursor()); + } + } + } else { + // 鼠标不在面板内,恢复默认光标 + setCursor(Cursor.getDefaultCursor()); + } + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + Graphics2D g2d = (Graphics2D) g.create(); + try { + BufferedImage imgToDraw = glContextManager.getCurrentFrame() + != null ? glContextManager.getCurrentFrame() : glContextManager.getLastFrame(); + int panelW = getWidth(); + int panelH = getHeight(); + if (imgToDraw != null) { + g2d.drawImage(imgToDraw, 0, 0, panelW, panelH, null); + } else { + g2d.setColor(Color.DARK_GRAY); + g2d.fillRect(0, 0, panelW, panelH); + } + if (modelRef.get() == null) { + g2d.setColor(new Color(255, 255, 0, 200)); + g2d.drawString("模型未加载", 10, 20); + } + } finally { + g2d.dispose(); + } + } + + /** + * 设置模型 + */ + public void setModel(Model2D model) { + glContextManager.executeInGLContext(() -> { + modelRef.set(model); + logger.info("模型已更新"); + }); + } + + /** + * 获取当前渲染的模型 + */ + public Model2D getModel() { + return modelRef.get(); + } + + /** + * 重新设置面板大小 + *

+ * 说明:当 Swing 面板被放大时,需要同时调整离屏 GLFW 窗口像素大小、GL 视口以及重分配像素读取缓冲, + * 否则将把较小分辨率的图像拉伸到更大面板上导致模糊。 + */ + public void resize(int newWidth, int newHeight) { + // 更新 Swing 尺寸 + setPreferredSize(new Dimension(newWidth, newHeight)); + revalidate(); + glContextManager.resize(newWidth, newHeight); + } + + /** + * 获取全局操作历史管理器 + */ + public StatusRecordManagement getStatusRecordManagement() { + return statusRecordManagement; + } + + /** + * 获取 GL 上下文管理器 + */ + public GLContextManager getGlContextManager() { + return glContextManager; + } + + /** + * 获取鼠标管理器 + */ + public MouseManagement getMouseManagement() { + return mouseManagement; + } + + /** + * 获取相机管理器 + */ + public CameraManagement getCameraManagement() { + return cameraManagement; + } + + /** + * 获取键盘管理器 + */ + public KeyboardManager getKeyboardManager() { + return keyboardManager; + } + + // ================== 保留的辅助方法 ================== + /** * 通过网格查找对应的 ModelPart */ - private ModelPart findPartByMesh(Mesh2D mesh) { + public ModelPart findPartByMesh(Mesh2D mesh) { Model2D model = modelRef.get(); if (model == null) return null; - for (int i = 0; i < model.getParts().size(); i++) { - ModelPart part = model.getParts().get(i); + for (ModelPart part : model.getParts()) { ModelPart found = findPartByMeshRecursive(part, mesh); if (found != null) { return found; @@ -2730,407 +656,20 @@ public class ModelRenderPanel extends JPanel { return null; } - /** - * 处理鼠标点击事件 - */ - private void handleMouseClick(MouseEvent e) { - if (!glContextManager.isContextInitialized()) return; - - final int screenX = e.getX(); - final int screenY = e.getY(); - - long currentTime = System.currentTimeMillis(); - boolean isDoubleClick = (currentTime - lastClickTime) < DOUBLE_CLICK_INTERVAL; - lastClickTime = currentTime; - - if (isDoubleClick) { - // 取消单单击计时器 - doubleClickTimer.stop(); - handleDoubleClick(e); - } else { - glContextManager.executeInGLContext(() -> { - try { - // 转换屏幕坐标到模型坐标 - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - logger.debug("点击位置:({}, {})", modelX, modelY); - // 检测点击的网格 - Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY); - - if (clickedMesh == null && !e.isControlDown() && !e.isShiftDown()) { - clearSelectedMeshes(); - logger.debug("点击空白处,取消所有选择"); - } - - // 触发点击事件 - for (ModelClickListener listener : clickListeners) { - try { - listener.onModelClicked(clickedMesh, modelX, modelY, screenX, screenY); - } catch (Exception ex) { - logger.error("点击事件监听器执行出错", ex); - } - } - } catch (Exception ex) { - logger.error("处理鼠标点击时出错", ex); - } - }); - doubleClickTimer.restart(); - } - } - - /** - * 处理鼠标移动事件 - */ - private void handleMouseMove(MouseEvent e) { - if (!glContextManager.isContextInitialized()) return; - - final int screenX = e.getX(); - final int screenY = e.getY(); - - if (glContextManager.isCameraDragging()) { - return; - } - - // 在 GL 上下文线程中执行悬停检测 - glContextManager.executeInGLContext(() -> { - try { - // 转换屏幕坐标到模型坐标 - float[] modelCoords = screenToModelCoordinates(screenX, screenY); - if (modelCoords == null) return; - - float modelX = modelCoords[0]; - float modelY = modelCoords[1]; - - // 木偶工具模式下的鼠标移动处理 - if (puppetMode) { - handlePuppetMouseMove(modelX, modelY); - return; - } - - // 二级顶点模式下的鼠标移动处理 - if (secondaryVertexMode) { - handleSecondaryVertexMouseMove(modelX, modelY); - return; - } - - // 检测悬停的网格 - Mesh2D newHoveredMesh = findMeshAtPosition(modelX, modelY); - - // 如果悬停网格发生变化,更新状态 - if (newHoveredMesh != hoveredMesh) { - if (hoveredMesh != null) { - hoveredMesh.setSuspension(false); - hoveredMesh.setRenderVertices(false); - } - - hoveredMesh = newHoveredMesh; - if (hoveredMesh != null) { - hoveredMesh.setSuspension(true); - } - - for (ModelClickListener listener : clickListeners) { - try { - listener.onModelHover(newHoveredMesh, modelX, modelY, screenX, screenY); - } catch (Exception ex) { - logger.error("悬停事件监听器执行出错", ex); - } - } - } - - updateCursorForHoverState(modelX, modelY, newHoveredMesh); - - } catch (Exception ex) { - logger.error("处理鼠标移动时出错", ex); - } - }); - } - - /** - * 根据悬停状态更新光标(无坐标版本,用于鼠标释放后) - */ - private void updateCursorForHoverState() { - Point mousePos = getMousePosition(); - if (mousePos != null) { - float[] modelCoords = screenToModelCoordinates(mousePos.x, mousePos.y); - if (modelCoords != null) { - updateCursorForHoverState(modelCoords[0], modelCoords[1], hoveredMesh); - } - } else { - // 鼠标不在面板内,恢复默认光标 - setCursor(Cursor.getDefaultCursor()); - } - } - - /** - * 根据悬停状态更新光标 - */ - private void updateCursorForHoverState(float modelX, float modelY, Mesh2D hoveredMesh) { - // 如果正在拖拽,不更新光标 - if (currentDragMode != DragMode.NONE) { - return; - } - - // 二级顶点模式下的光标处理 - if (secondaryVertexMode) { - if (hoveredSecondaryVertex != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else { - setCursor(createSecondaryVertexCursor()); - } - return; - } - - // 木偶工具模式下的光标处理 - if (puppetMode) { - if (hoveredPuppetPin != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else { - setCursor(createPuppetCursor()); - } - return; - } - - Cursor newCursor = Cursor.getDefaultCursor(); - isOverSelection = false; - - // 检查是否在选中的网格上 - if (!selectedMeshes.isEmpty()) { - // 多选时只对最后一个选中的网格进行操作 - Mesh2D targetMeshForHandle = lastSelectedMesh; - if (targetMeshForHandle != null) { - DragMode hoverMode = checkResizeHandleHit(modelX, modelY, targetMeshForHandle); - if (hoverMode != DragMode.NONE) { - newCursor = getCursorForDragMode(hoverMode); - isOverSelection = true; - } else { - // 检查是否在选中网格的边界框内(但不是调整手柄) - BoundingBox bounds; - if (targetMeshForHandle.isInMultiSelection()) { - bounds = targetMeshForHandle.getMultiSelectionBounds(); - } else { - bounds = targetMeshForHandle.getBounds(); - } - - if (modelX >= bounds.getMinX() && modelX <= bounds.getMaxX() && - modelY >= bounds.getMinY() && modelY <= bounds.getMaxY()) { - newCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); - isOverSelection = true; - } - } - } - } - - // 如果没有在选中的网格上,检查是否在可悬停的网格上 - if (!isOverSelection && hoveredMesh != null) { - newCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); - } - - // 更新光标 - if (!newCursor.equals(currentCursor)) { - currentCursor = newCursor; - Cursor finalNewCursor = newCursor; - SwingUtilities.invokeLater(() -> setCursor(finalNewCursor)); - } - } - - /** - * 获取当前木偶工具模式状态 - */ - public boolean isInPuppetMode() { - return puppetMode; - } - - /** - * 获取木偶工具目标网格 - */ - public Mesh2D getPuppetTargetMesh() { - return puppetTargetMesh; - } - - /** - * 将屏幕坐标转换为模型坐标 - */ - public float[] screenToModelCoordinates(int screenX, int screenY) { - if (!glContextManager.isContextInitialized() || glContextManager.getWidth() <= 0 || glContextManager.getHeight() <= 0) return null; - float glX = (float) screenX * glContextManager.getWidth() / getWidth(); - float glY = (float) screenY * glContextManager.getHeight() / getHeight(); - float ndcX = (2.0f * glX) / glContextManager.getWidth() - 1.0f; - float ndcY = 1.0f - (2.0f * glY) / glContextManager.getHeight(); - Vector2f camOffset = ModelRender.getCameraOffset(); - float zoom = ModelRender.getCamera().getZoom(); - float modelX = (ndcX * glContextManager.getWidth() / (2.0f * zoom)) + camOffset.x; - float modelY = (ndcY * glContextManager.getHeight() / (-2.0f * zoom)) + camOffset.y; - return new float[]{modelX, modelY}; - } - - /** - * 在指定位置查找网格 - */ - private Mesh2D findMeshAtPosition(float modelX, float modelY) { - Model2D model = modelRef.get(); - if (model == null) { - logger.debug("模型未加载"); - return null; - } - - try { - List parts = model.getParts(); - if (parts == null || parts.isEmpty()) { - return null; - } - - // 遍历所有部件和网格(从上到下) - for (int i = parts.size() - 1; i >= 0; i--) { - ModelPart part = parts.get(i); - if (part == null || !part.isVisible()) continue; - - List meshes = part.getMeshes(); - if (meshes == null || meshes.isEmpty()) continue; - - for (int m = meshes.size() - 1; m >= 0; m--) { - Mesh2D mesh = meshes.get(m); - if (mesh == null || !mesh.isVisible()) continue; - - if (mesh.isDirty()) { - mesh.updateBounds(); - } - - boolean contains = false; - try { - contains = mesh.containsPoint(modelX, modelY); - } catch (Exception ex) { - logger.warn("mesh.containsPoint 抛出异常: {}", ex.getMessage()); - } - - if (contains) { - return mesh; - } - } - } - return null; - } catch (Exception e) { - logger.error("检测网格时出错", e); - return null; - } - } - - /** - * 获取模型的边界框 - */ - private BoundingBox getModelBounds(Model2D model) { - if (model == null) return null; - - try { - Method getBoundsMethod = model.getClass().getMethod("getBounds"); - return (BoundingBox) getBoundsMethod.invoke(model); - } catch (Exception e) { - logger.debug("无法获取模型边界", e); - return null; - } - } - - /** - * 创建错误模型作为回退 - */ - private void createErrorModel() { - try { - // 这里可以创建一个简单的默认模型 - // 或者保持 modelRef 为 null,在渲染时显示错误信息 - System.out.println("使用默认错误模型"); - } catch (Exception e) { - System.err.println("创建错误模型失败: " + e.getMessage()); - } - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - Graphics2D g2d = (Graphics2D) g.create(); - try { - // 选择要绘制的图像:优先 currentFrame(最新),其不存在则用 lastFrame(最后成功帧) - BufferedImage imgToDraw = glContextManager.getCurrentFrame() - != null ? glContextManager.getCurrentFrame() : glContextManager.getLastFrame(); - - int panelW = getWidth(); - int panelH = getHeight(); - - if (imgToDraw != null) { - g2d.drawImage(imgToDraw, 0, 0, panelW, panelH, null); - } else { - g2d.setColor(Color.DARK_GRAY); - g2d.fillRect(0, 0, panelW, panelH); - } - if (modelRef.get() == null) { - g2d.setColor(new Color(255, 255, 0, 200)); - g2d.drawString("模型未加载", 10, 20); - } - } finally { - g2d.dispose(); - } - } - - // ================== 新增:GL 上下文任务执行方法 ================== - - /** - * 设置模型(线程安全)- 使用新的 GL 上下文执行方法 - */ - public void setModel(Model2D model) { - glContextManager.executeInGLContext(() -> { - modelRef.set(model); - logger.info("模型已更新"); - }); - } - - /** - * 获取当前渲染的模型 - */ - public Model2D getModel() { - return modelRef.get(); - } - - /** - * 重新设置面板大小 - *

- * 说明:当 Swing 面板被放大时,需要同时调整离屏 GLFW 窗口像素大小、GL 视口以及重分配像素读取缓冲, - * 否则将把较小分辨率的图像拉伸到更大面板上导致模糊。 - */ - public void resize(int newWidth, int newHeight) { - // 更新 Swing 尺寸 - setPreferredSize(new Dimension(newWidth, newHeight)); - revalidate(); - glContextManager.resize(newWidth, newHeight); - } - - /** - * 设置操作历史管理器(兼容性方法) - */ - public void setHistoryManager(OperationHistoryManager manager) { - this.historyManager = manager; - } - - /** - * 获取操作历史管理器(兼容性方法) - */ - public OperationHistoryManager getHistoryManager() { - return historyManager; - } - - /** - * 获取全局操作历史管理器 - */ - public OperationHistoryGlobal getOperationHistory() { - return operationHistory; - } - - /** - * 获取 GL 上下文管理器 - */ - public GLContextManager getGlContextManager() { - return glContextManager; + public enum DragMode { + NONE, // 无拖拽 + MOVE, // 移动部件 + RESIZE_LEFT, // 调整左边 + RESIZE_RIGHT, // 调整右边 + RESIZE_TOP, // 调整上边 + RESIZE_BOTTOM, // 调整下边 + RESIZE_TOP_LEFT, // 调整左上角 + RESIZE_TOP_RIGHT, // 调整右上角 + RESIZE_BOTTOM_LEFT, // 调整左下角 + RESIZE_BOTTOM_RIGHT, // 调整右下角 + ROTATE, // 新增:旋转 + MOVE_PIVOT, // 新增:移动中心点 + MOVE_SECONDARY_VERTEX, // 新增:移动二级顶点 + MOVE_PUPPET_PIN // 新增:移动 puppetPin } } \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/CameraManagement.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/CameraManagement.java new file mode 100644 index 0000000..88b19f2 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/CameraManagement.java @@ -0,0 +1,99 @@ +package com.chuangzhou.vivid2D.render.awt.manager; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.systems.Camera; +import org.joml.Vector2f; + +public class CameraManagement { + private final ModelRenderPanel modelRenderPanel; + private final GLContextManager glContextManager; + private final WorldManagement worldManagement; + + private volatile int lastCameraDragX, lastCameraDragY; + private final Vector2f rotationCenter = new Vector2f(); + + public static final float ZOOM_STEP = 1.15f; // 每格滚轮的指数因子(>1 放大) + public static final float ZOOM_MIN = 0.1f; + public static final float ZOOM_MAX = 8.0f; + public static final float ROTATION_HANDLE_DISTANCE = 30.0f; + + public CameraManagement(ModelRenderPanel modelRenderPanel, GLContextManager glContextManager, WorldManagement worldManagement){ + this.modelRenderPanel = modelRenderPanel; + this.glContextManager = glContextManager; + this.worldManagement = worldManagement; + } + + public void resizingApplications(int screenX, int screenY, int notches, boolean fine){ + glContextManager.executeInGLContext(() -> { + Camera camera = ModelRender.getCamera(); + float oldZoom = camera.getZoom(); + float[] worldPosBefore = worldManagement.screenToModelCoordinates(screenX, screenY); + if (worldPosBefore == null) return; + double step = fine ? Math.pow(ZOOM_STEP, 0.25) : ZOOM_STEP; + float newZoom = oldZoom; + if (notches > 0) { // 缩小 + newZoom /= (float) Math.pow(step, notches); + } else { // 放大 + newZoom *= (float) Math.pow(step, -notches); + } + newZoom = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, newZoom)); + if (Math.abs(newZoom - oldZoom) < 1e-6f) { + return; + } + camera.setZoom(newZoom); + float[] worldPosAfter = worldManagement.screenToModelCoordinates(screenX, screenY); + if (worldPosAfter == null) { + camera.setZoom(oldZoom); + return; + } + float panX = worldPosBefore[0] - worldPosAfter[0]; + float panY = worldPosBefore[1] - worldPosAfter[1]; + camera.move(panX, panY); + glContextManager.setDisplayScale(newZoom); + glContextManager.setTargetScale(newZoom); + }); + } + + /** + * 计算当前缩放因子(模型单位与屏幕像素的比例) + */ + public float calculateScaleFactor() { + int panelWidth = modelRenderPanel.getWidth(); + int panelHeight = modelRenderPanel.getHeight(); + + if (panelWidth <= 0 || panelHeight <= 0 || glContextManager.getHeight() <= 0 || glContextManager.getHeight() <= 0) { + return 1.0f; + } + + // 计算面板与离屏缓冲区的比例 + float scaleX = (float) panelWidth / glContextManager.getWidth(); + float scaleY = (float) panelHeight / glContextManager.getHeight(); + + // 基本面板缩放(保持与现有逻辑一致) + float base = Math.min(scaleX, scaleY); + + // 乘以平滑的 displayScale,使视觉上缩放与检测区域一致 + return base * glContextManager.displayScale; + } + + public int getLastCameraDragX() { + return lastCameraDragX; + } + + public int getLastCameraDragY() { + return lastCameraDragY; + } + + public void setLastCameraDragX(int lastCameraDragX) { + this.lastCameraDragX = lastCameraDragX; + } + + public void setLastCameraDragY(int lastCameraDragY) { + this.lastCameraDragY = lastCameraDragY; + } + + public Vector2f getRotationCenter() { + return rotationCenter; + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/KeyboardManager.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/KeyboardManager.java new file mode 100644 index 0000000..76638c8 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/KeyboardManager.java @@ -0,0 +1,423 @@ +package com.chuangzhou.vivid2D.render.awt.manager; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.awt.tools.LiquifyTool; +import com.chuangzhou.vivid2D.render.awt.tools.Tool; +import com.chuangzhou.vivid2D.render.model.util.tools.LiquifyTargetPartRander; +import com.chuangzhou.vivid2D.render.systems.Camera; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.HashMap; +import java.util.Map; + +public class KeyboardManager { + private static final Logger logger = LoggerFactory.getLogger(KeyboardManager.class); + private final ModelRenderPanel panel; + private volatile boolean shiftPressed = false; + private volatile boolean ctrlPressed = false; + + // 存储自定义快捷键 + private final Map customShortcuts = new HashMap<>(); + private final Map customActions = new HashMap<>(); + + public KeyboardManager(ModelRenderPanel panel){ + this.panel = panel; + } + + /** + * 初始化键盘快捷键 + */ + public void initKeyboardShortcuts() { + // 获取输入映射和动作映射 + InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = panel.getActionMap(); + + // 撤回快捷键:Ctrl+Z + registerShortcut("undo", KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.getStatusRecordManagement().undo(); + } + }); + + // 重做快捷键:Ctrl+Y 或 Ctrl+Shift+Z + registerShortcut("redo", KeyStroke.getKeyStroke(KeyEvent.VK_Y, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.getStatusRecordManagement().redo(); + } + }); + registerShortcut("redo2", KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.getStatusRecordManagement().redo(); + } + }); + + // 清除历史记录:Ctrl+Shift+Delete + registerShortcut("clearHistory", KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.getStatusRecordManagement().clearHistory(); + } + }); + + // 摄像机重置快捷键:Ctrl+R + registerShortcut("resetCamera", KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.resetCamera(); + logger.info("重置摄像机"); + } + }); + + // 摄像机启用/禁用快捷键:Ctrl+E + registerShortcut("toggleCamera", KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + Camera camera = ModelRender.getCamera(); + boolean newState = !camera.isEnabled(); + camera.setEnabled(newState); + logger.info("{}摄像机", newState ? "启用" : "禁用"); + } + }); + + // 注册工具快捷键 + registerToolShortcuts(); + + // 设置键盘监听器 + setupKeyListeners(); + } + + /** + * 注册工具快捷键 + */ + private void registerToolShortcuts() { + // 木偶变形工具快捷键:Ctrl+P + registerShortcut("puppetTool", KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.switchTool("木偶变形工具"); + logger.info("切换到木偶变形工具"); + } + }); + + // 顶点变形工具快捷键:Ctrl+T + registerShortcut("vertexTool", KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.switchTool("顶点变形工具"); + logger.info("切换到顶点变形工具"); + } + }); + + // 选择工具快捷键:Ctrl+S + registerShortcut("selectionTool", KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK), + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.switchTool("选择工具"); + logger.info("切换到选择工具"); + } + }); + } + + /** + * 注册自定义快捷键 + * @param actionName 动作名称 + * @param keyStroke 按键组合 + * @param action 对应的动作 + */ + public void registerShortcut(String actionName, KeyStroke keyStroke, AbstractAction action) { + InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = panel.getActionMap(); + + // 如果已存在相同的快捷键,先移除 + if (customShortcuts.containsKey(actionName)) { + KeyStroke oldKeyStroke = customShortcuts.get(actionName); + inputMap.remove(oldKeyStroke); + } + + // 注册新的快捷键 + inputMap.put(keyStroke, actionName); + actionMap.put(actionName, action); + + // 保存到自定义快捷键映射 + customShortcuts.put(actionName, keyStroke); + customActions.put(actionName, action); + + logger.debug("注册快捷键: {} -> {}", keyStroke, actionName); + } + + /** + * 注销自定义快捷键 + * @param actionName 动作名称 + */ + public void unregisterShortcut(String actionName) { + InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = panel.getActionMap(); + + if (customShortcuts.containsKey(actionName)) { + KeyStroke keyStroke = customShortcuts.get(actionName); + inputMap.remove(keyStroke); + actionMap.remove(actionName); + + customShortcuts.remove(actionName); + customActions.remove(actionName); + + logger.debug("注销快捷键: {}", actionName); + } + } + + /** + * 注册工具快捷键 + * @param toolName 工具名称 + * @param keyStroke 按键组合 + */ + public void registerToolShortcut(String toolName, KeyStroke keyStroke) { + String actionName = "tool_" + toolName; + registerShortcut(actionName, keyStroke, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.switchTool(toolName); + logger.info("切换到工具: {}", toolName); + } + }); + } + + /** + * 注册工具循环切换快捷键 + * @param keyStroke 按键组合 + */ + public void registerToolCycleShortcut(KeyStroke keyStroke) { + registerShortcut("cycleTools", keyStroke, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + panel.getToolManagement().switchToPreviousTool(); + Tool currentTool = panel.getCurrentTool(); + if (currentTool != null) { + logger.info("切换到上一个工具: {}", currentTool.getToolName()); + } + } + }); + } + + /** + * 设置键盘监听器 + */ + private void setupKeyListeners() { + panel.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + handleKeyPressed(e); + } + + @Override + public void keyReleased(KeyEvent e) { + handleKeyReleased(e); + } + }); + } + + /** + * 处理按键按下 + */ + private void handleKeyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + + // 更新修饰键状态 + if (keyCode == KeyEvent.VK_SHIFT) { + shiftPressed = true; + } else if (keyCode == KeyEvent.VK_CONTROL) { + ctrlPressed = true; + // 液化模式下按住Ctrl显示顶点 + if (panel.getCurrentTool() instanceof LiquifyTool liquifyTool) { + LiquifyTargetPartRander rander = liquifyTool.getAssociatedRanderTools(); + if (rander != null) { + rander.setAlgorithmEnabled("isRenderVertices",true); + logger.debug("液化模式下按住Ctrl,开启顶点渲染"); + } + } + } + + // 处理功能快捷键 + if (ctrlPressed) { + switch (keyCode) { + case KeyEvent.VK_A: + // Ctrl+A 全选 + e.consume(); + panel.selectAllMeshes(); + logger.debug("全选所有网格"); + break; + case KeyEvent.VK_D: + // Ctrl+D 取消选择 + e.consume(); + panel.clearSelectedMeshes(); + logger.debug("取消所有选择"); + break; + case KeyEvent.VK_1: + // Ctrl+1 切换到第一个工具 + e.consume(); + switchToToolByIndex(0); + break; + case KeyEvent.VK_2: + // Ctrl+2 切换到第二个工具 + e.consume(); + switchToToolByIndex(1); + break; + case KeyEvent.VK_3: + // Ctrl+3 切换到第三个工具 + e.consume(); + switchToToolByIndex(2); + break; + } + } + + // 单独按键处理 + switch (keyCode) { + case KeyEvent.VK_ESCAPE: + // ESC 键取消所有选择或退出工具 + e.consume(); + if (panel.getToolManagement().hasActiveTool() && + !panel.getToolManagement().getCurrentTool().getToolName().equals("选择工具")) { + // 如果有激活的工具且不是选择工具,切换到选择工具 + panel.switchTool("选择工具"); + logger.info("按ESC键切换到选择工具"); + } else { + // 否则取消所有选择 + panel.clearSelectedMeshes(); + + logger.info("按ESC键取消所有选择"); + } + break; + case KeyEvent.VK_SPACE: + // 空格键临时切换到手型工具(用于移动视图) + if (!e.isConsumed()) { + // 这里可以添加空格键拖拽视图的功能 + // 需要与鼠标管理中键拖拽功能配合 + } + break; + } + } + + /** + * 处理按键释放 + */ + private void handleKeyReleased(KeyEvent e) { + int keyCode = e.getKeyCode(); + + // 更新修饰键状态 + if (keyCode == KeyEvent.VK_SHIFT) { + shiftPressed = false; + } else if (keyCode == KeyEvent.VK_CONTROL) { + ctrlPressed = false; + // 液化模式下松开Ctrl隐藏顶点 + if (panel.getCurrentTool() instanceof LiquifyTool liquifyTool) { + LiquifyTargetPartRander rander = liquifyTool.getAssociatedRanderTools(); + if (rander != null) { + rander.setAlgorithmEnabled("isRenderVertices",false); + logger.debug("液化模式下松开Ctrl,关闭顶点渲染"); + } + } + } + } + + /** + * 根据索引切换到工具 + */ + private void switchToToolByIndex(int index) { + java.util.List tools = panel.getToolManagement().getRegisteredTools(); + if (index >= 0 && index < tools.size()) { + Tool tool = tools.get(index); + panel.switchTool(tool.getToolName()); + logger.info("切换到工具: {}", tool.getToolName()); + } + } + + /** + * 获取所有注册的快捷键信息 + */ + public Map getShortcutInfo() { + Map info = new HashMap<>(); + for (Map.Entry entry : customShortcuts.entrySet()) { + info.put(entry.getKey(), entry.getValue().toString()); + } + return info; + } + + /** + * 重新加载所有快捷键 + */ + public void reloadShortcuts() { + // 清除所有自定义快捷键 + InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = panel.getActionMap(); + + for (String actionName : customShortcuts.keySet()) { + KeyStroke keyStroke = customShortcuts.get(actionName); + inputMap.remove(keyStroke); + actionMap.remove(actionName); + } + + customShortcuts.clear(); + customActions.clear(); + + // 重新初始化 + initKeyboardShortcuts(); + logger.info("重新加载所有快捷键"); + } + + /** + * 获取Shift键状态 + */ + public boolean getIsShiftPressed(){ + return shiftPressed; + } + + /** + * 获取Ctrl键状态 + */ + public boolean getIsCtrlPressed(){ + return ctrlPressed; + } + + /** + * 清理资源 + */ + public void dispose() { + // 移除所有键盘监听器 + for (KeyListener listener : panel.getKeyListeners()) { + panel.removeKeyListener(listener); + } + + // 清除所有快捷键 + InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = panel.getActionMap(); + + for (String actionName : customShortcuts.keySet()) { + KeyStroke keyStroke = customShortcuts.get(actionName); + inputMap.remove(keyStroke); + actionMap.remove(actionName); + } + + customShortcuts.clear(); + customActions.clear(); + + logger.info("键盘管理器已清理"); + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/MouseManagement.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/MouseManagement.java new file mode 100644 index 0000000..77c06b5 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/MouseManagement.java @@ -0,0 +1,92 @@ +package com.chuangzhou.vivid2D.render.awt.manager; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; + +import java.awt.*; +import java.awt.event.*; + +public class MouseManagement { + + private final ModelRenderPanel modelRenderPanel; + private final GLContextManager glContextManager; + private final CameraManagement cameraManagement; + private final KeyboardManager keyboardManager; + + public MouseManagement(ModelRenderPanel modelRenderPanel, + GLContextManager glContextManager, + CameraManagement cameraManagement, + KeyboardManager keyboardManager){ + this.modelRenderPanel = modelRenderPanel; + this.glContextManager = glContextManager; + this.cameraManagement = cameraManagement; + this.keyboardManager = keyboardManager; + } + + /** + * 添加鼠标事件监听器 + */ + public void addMouseListeners() { + modelRenderPanel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + modelRenderPanel.handleMouseClick(e); + } + + @Override + public void mousePressed(MouseEvent e) { + modelRenderPanel.handleMousePressed(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + modelRenderPanel.handleMouseReleased(e); + } + + @Override + public void mouseExited(MouseEvent e) { + modelRenderPanel.setCursor(Cursor.getDefaultCursor()); + } + }); + + modelRenderPanel.addMouseWheelListener(new MouseWheelListener() { + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + if (!glContextManager.isContextInitialized()) return; + final int screenX = e.getX(); + final int screenY = e.getY(); + final int notches = e.getWheelRotation(); + final boolean fine = e.isShiftDown(); + cameraManagement.resizingApplications(screenX, screenY, notches, fine); + } + }); + + modelRenderPanel.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + modelRenderPanel.handleMouseMove(e); + } + + @Override + public void mouseDragged(MouseEvent e) { + modelRenderPanel.handleMouseDragged(e); + } + }); + + modelRenderPanel.addMouseWheelListener(e -> { + int notches = e.getWheelRotation(); + boolean fine = (e.isShiftDown() || keyboardManager.getIsShiftPressed()); // 支持 Shift 更精细控制 + double step = fine ? Math.pow(CameraManagement.ZOOM_STEP, 0.25) : CameraManagement.ZOOM_STEP; + if (notches > 0) { + // 滚轮下:缩小 + glContextManager.targetScale *= Math.pow(1.0 / step, notches); + } else if (notches < 0) { + // 滚轮上:放大 + glContextManager.targetScale *= Math.pow(step, -notches); + } + glContextManager.targetScale = Math.max(CameraManagement.ZOOM_MIN, Math.min(CameraManagement.ZOOM_MAX, glContextManager.targetScale)); + }); + modelRenderPanel.setFocusable(true); + modelRenderPanel.requestFocusInWindow(); + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/StatusRecordManagement.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/StatusRecordManagement.java new file mode 100644 index 0000000..cc6d384 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/StatusRecordManagement.java @@ -0,0 +1,164 @@ +package com.chuangzhou.vivid2D.render.awt.manager; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.awt.util.OperationHistoryGlobal; +import com.chuangzhou.vivid2D.render.model.ModelPart; +import org.joml.Vector2f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class StatusRecordManagement { + private final ModelRenderPanel panel; + private final OperationHistoryGlobal operationHistory; + + public StatusRecordManagement(ModelRenderPanel panel, OperationHistoryGlobal operationHistory){ + this.operationHistory = operationHistory; + this.panel = panel; + } + + /** + * 重做操作 + */ + public void redo() { + if (operationHistory != null && operationHistory.canRedo()) { + panel.getGlContextManager().executeInGLContext(() -> { + boolean success = operationHistory.redo(); + if (success) { + panel.repaint(); + System.out.println("重做: " + operationHistory.getRedoDescription()); + } + }); + } else { + System.out.println("没有可重做的操作"); + } + } + + /** + * 记录位置变化操作 + */ + private void recordPositionChange(ModelPart part, Vector2f oldPosition, Vector2f newPosition) { + if (operationHistory != null && part != null) { + operationHistory.recordOperation("SET_POSITION", part, oldPosition, newPosition); + } + } + + /** + * 记录缩放变化操作 + */ + private void recordScaleChange(ModelPart part, Vector2f oldScale, Vector2f newScale) { + if (operationHistory != null && part != null) { + operationHistory.recordOperation("SET_SCALE", part, oldScale, newScale); + } + } + + /** + * 记录旋转变化操作 + */ + private void recordRotationChange(ModelPart part, float oldRotation, float newRotation) { + if (operationHistory != null && part != null) { + operationHistory.recordOperation("SET_ROTATION", part, oldRotation, newRotation); + } + } + + /** + * 记录中心点变化操作 + */ + private void recordPivotChange(ModelPart part, Vector2f oldPivot, Vector2f newPivot) { + if (operationHistory != null && part != null) { + operationHistory.recordOperation("SET_PIVOT", part, oldPivot, newPivot); + } + } + + /** + * 记录拖拽结束操作 + */ + public void recordDragEnd(List parts, Map startPositions) { + if (operationHistory != null && parts != null && !parts.isEmpty()) { + List params = new ArrayList<>(); + params.add(parts); + params.add(startPositions); + // 添加当前位置 + for (ModelPart part : parts) { + params.add(part.getPosition()); + } + operationHistory.recordOperation("DRAG_PART_END", params.toArray()); + } + } + + /** + * 记录调整大小结束操作 + */ + public void recordResizeEnd(List parts, Map startScales) { + if (operationHistory != null && parts != null && !parts.isEmpty()) { + List params = new ArrayList<>(); + params.add(parts); + params.add(startScales); + // 添加当前缩放 + for (ModelPart part : parts) { + params.add(part.getScale()); + } + operationHistory.recordOperation("RESIZE_PART_END", params.toArray()); + } + } + + /** + * 记录旋转结束操作 + */ + public void recordRotateEnd(List parts, Map startRotations) { + if (operationHistory != null && parts != null && !parts.isEmpty()) { + List params = new ArrayList<>(); + params.add(parts); + params.add(startRotations); + // 添加当前旋转 + for (ModelPart part : parts) { + params.add(part.getRotation()); + } + operationHistory.recordOperation("ROTATE_PART_END", params.toArray()); + } + } + + /** + * 记录移动中心点结束操作 + */ + public void recordMovePivotEnd(List parts, Map startPivots) { + if (operationHistory != null && parts != null && !parts.isEmpty()) { + List params = new ArrayList<>(); + params.add(parts); + params.add(startPivots); + // 添加当前中心点 + for (ModelPart part : parts) { + params.add(part.getPivot()); + } + operationHistory.recordOperation("MOVE_PIVOT_END", params.toArray()); + } + } + + /** + * 撤回操作 + */ + public void undo() { + if (operationHistory != null && operationHistory.canUndo()) { + panel.getGlContextManager().executeInGLContext(() -> { + boolean success = operationHistory.undo(); + if (success) { + panel.repaint(); + System.out.println("撤回: " + operationHistory.getUndoDescription()); + } + }); + } else { + System.out.println("没有可撤回的操作"); + } + } + + /** + * 清除操作历史 + */ + public void clearHistory() { + if (operationHistory != null) { + operationHistory.clearHistory(); + System.out.println("操作历史已清除"); + } + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ToolManagement.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ToolManagement.java new file mode 100644 index 0000000..fcb5b98 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ToolManagement.java @@ -0,0 +1,329 @@ +package com.chuangzhou.vivid2D.render.awt.manager; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.awt.tools.SelectionTool; +import com.chuangzhou.vivid2D.render.awt.tools.Tool; +import com.chuangzhou.vivid2D.render.model.util.manager.RanderToolsManager; +import com.chuangzhou.vivid2D.render.model.util.tools.RanderTools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 工具管理器 + * 负责注册、管理和切换各种编辑工具 + */ +public class ToolManagement { + private static final Logger logger = LoggerFactory.getLogger(ToolManagement.class); + + private final ModelRenderPanel renderPanel; + private final Map registeredTools; + private final RanderToolsManager randerToolsManager; + private Tool currentTool = null; + private Tool previousTool = null; + + // 默认工具(选择工具) + private final Tool defaultTool; + + public ToolManagement(ModelRenderPanel renderPanel, RanderToolsManager randerToolsManager) { + this.renderPanel = renderPanel; + this.registeredTools = new ConcurrentHashMap<>(); + this.randerToolsManager = randerToolsManager; + + // 创建默认选择工具 + this.defaultTool = new SelectionTool(renderPanel); + registerTool(defaultTool); + + // 设置默认工具为当前工具 + switchTool(defaultTool.getToolName()); + } + + // ================== 工具注册管理 ================== + + /** + * 注册工具 + */ + public void registerTool(Tool tool, RanderTools randerTools) { + if (tool == null) { + logger.warn("尝试注册空工具"); + return; + } + + String toolName = tool.getToolName(); + if (registeredTools.containsKey(toolName)) { + logger.warn("工具已存在: {}", toolName); + return; + } + registeredTools.put(toolName, tool); + randerToolsManager.bindToolWithRanderTools(tool, randerTools); + tool.setAssociatedRanderTools(randerTools); + logger.info("注册工具: {}", toolName); + } + + /** + * 注册工具 + */ + public void registerTool(Tool tool) { + if (tool == null) { + logger.warn("尝试注册空工具"); + return; + } + + String toolName = tool.getToolName(); + if (registeredTools.containsKey(toolName)) { + logger.warn("工具已存在: {}", toolName); + return; + } + + registeredTools.put(toolName, tool); + logger.info("注册工具: {}", toolName); + } + + /** + * 注销工具 + */ + public void unregisterTool(String toolName) { + Tool tool = registeredTools.get(toolName); + if (tool == null) { + logger.warn("工具不存在: {}", toolName); + return; + } + + // 如果要注销的工具是当前工具,先停用它 + if (currentTool == tool) { + switchToDefaultTool(); + } + + tool.dispose(); + registeredTools.remove(toolName); + logger.info("注销工具: {}", toolName); + } + + /** + * 获取所有注册的工具 + */ + public List getRegisteredTools() { + return new ArrayList<>(registeredTools.values()); + } + + /** + * 根据名称获取工具 + */ + public Tool getTool(String toolName) { + return registeredTools.get(toolName); + } + + // ================== 工具切换管理 ================== + + /** + * 切换到指定工具 + */ + public boolean switchTool(String toolName) { + Tool targetTool = registeredTools.get(toolName); + if (targetTool == null) { + logger.warn("工具不存在: {}", toolName); + return false; + } + + return switchTool(targetTool); + } + + /** + * 切换到指定工具实例 + */ + public boolean switchTool(Tool tool) { + if (tool == null) { + logger.warn("尝试切换到空工具"); + return false; + } + + // 检查工具是否可用 + if (!tool.isAvailable()) { + logger.warn("工具不可用: {}", tool.getToolName()); + return false; + } + + // 如果已经是当前工具,直接返回 + if (currentTool == tool) { + return true; + } + + // 停用当前工具 + if (currentTool != null) { + currentTool.deactivate(); + previousTool = currentTool; + } + + // 激活新工具 + currentTool = tool; + currentTool.activate(); + + // 更新光标 + updateCursor(); + + logger.info("切换到工具: {}", currentTool.getToolName()); + return true; + } + + /** + * 切换到默认工具 + */ + public void switchToDefaultTool() { + switchTool(defaultTool); + } + + /** + * 切换到上一个工具 + */ + public void switchToPreviousTool() { + if (previousTool != null && previousTool.isAvailable()) { + switchTool(previousTool); + } else { + switchToDefaultTool(); + } + } + + /** + * 获取当前工具 + */ + public Tool getCurrentTool() { + return currentTool; + } + + /** + * 获取上一个工具 + */ + public Tool getPreviousTool() { + return previousTool; + } + + /** + * 获取默认工具 + */ + public Tool getDefaultTool() { + return defaultTool; + } + + // ================== 事件转发 ================== + + /** + * 处理鼠标按下事件 + */ + public void handleMousePressed(MouseEvent e, float modelX, float modelY) { + if (currentTool != null) { + currentTool.onMousePressed(e, modelX, modelY); + } + } + + /** + * 处理鼠标释放事件 + */ + public void handleMouseReleased(MouseEvent e, float modelX, float modelY) { + if (currentTool != null) { + currentTool.onMouseReleased(e, modelX, modelY); + } + } + + /** + * 处理鼠标拖拽事件 + */ + public void handleMouseDragged(MouseEvent e, float modelX, float modelY) { + if (currentTool != null) { + currentTool.onMouseDragged(e, modelX, modelY); + } + } + + /** + * 处理鼠标移动事件 + */ + public void handleMouseMoved(MouseEvent e, float modelX, float modelY) { + if (currentTool != null) { + currentTool.onMouseMoved(e, modelX, modelY); + } + } + + /** + * 处理鼠标点击事件 + */ + public void handleMouseClicked(MouseEvent e, float modelX, float modelY) { + if (currentTool != null) { + currentTool.onMouseClicked(e, modelX, modelY); + } + } + + /** + * 处理鼠标双击事件 + */ + public void handleMouseDoubleClicked(MouseEvent e, float modelX, float modelY) { + if (currentTool != null) { + currentTool.onMouseDoubleClicked(e, modelX, modelY); + } + } + + // ================== 工具状态管理 ================== + + /** + * 更新光标 + */ + private void updateCursor() { + if (currentTool != null) { + Cursor cursor = currentTool.getToolCursor(); + if (cursor != null) { + renderPanel.setCursor(cursor); + } + } + } + + /** + * 检查是否有工具处于激活状态 + */ + public boolean hasActiveTool() { + return currentTool != null && currentTool.isActive(); + } + + /** + * 停用所有工具 + */ + public void deactivateAllTools() { + for (Tool tool : registeredTools.values()) { + if (tool.isActive()) { + tool.deactivate(); + } + } + currentTool = null; + renderPanel.setCursor(Cursor.getDefaultCursor()); + } + + /** + * 清理所有工具资源 + */ + public void dispose() { + deactivateAllTools(); + for (Tool tool : registeredTools.values()) { + tool.dispose(); + } + registeredTools.clear(); + logger.info("工具管理器已清理"); + } + + /** + * 获取工具统计信息 + */ + public String getToolStatistics() { + int activeCount = 0; + for (Tool tool : registeredTools.values()) { + if (tool.isActive()) { + activeCount++; + } + } + + return String.format("工具统计: 注册%d个, 激活%d个, 当前工具: %s", + registeredTools.size(), activeCount, + currentTool != null ? currentTool.getToolName() : "无"); + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/WorldManagement.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/WorldManagement.java new file mode 100644 index 0000000..5392984 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/WorldManagement.java @@ -0,0 +1,31 @@ +package com.chuangzhou.vivid2D.render.awt.manager; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import org.joml.Vector2f; + +public class WorldManagement { + private final ModelRenderPanel modelRenderPanel; + private final GLContextManager glContextManager; + + public WorldManagement(ModelRenderPanel modelRenderPanel, GLContextManager glContextManager) { + this.modelRenderPanel = modelRenderPanel; + this.glContextManager = glContextManager; + } + + /** + * 将屏幕坐标转换为模型坐标 + */ + public float[] screenToModelCoordinates(int screenX, int screenY) { + if (!glContextManager.isContextInitialized() || glContextManager.getWidth() <= 0 || glContextManager.getHeight() <= 0) return null; + float glX = (float) screenX * glContextManager.getWidth() / modelRenderPanel.getWidth(); + float glY = (float) screenY * glContextManager.getHeight() / modelRenderPanel.getHeight(); + float ndcX = (2.0f * glX) / glContextManager.getWidth() - 1.0f; + float ndcY = 1.0f - (2.0f * glY) / glContextManager.getHeight(); + Vector2f camOffset = ModelRender.getCameraOffset(); + float zoom = ModelRender.getCamera().getZoom(); + float modelX = (ndcX * glContextManager.getWidth() / (2.0f * zoom)) + camOffset.x; + float modelY = (ndcY * glContextManager.getHeight() / (-2.0f * zoom)) + camOffset.y; + return new float[]{modelX, modelY}; + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java new file mode 100644 index 0000000..4dc3b07 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/LiquifyTool.java @@ -0,0 +1,400 @@ +package com.chuangzhou.vivid2D.render.awt.tools; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.model.Model2D; +import com.chuangzhou.vivid2D.render.model.ModelPart; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.model.util.tools.LiquifyTargetPartRander; +import org.joml.Vector2f; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; + +/** + * 液化工具 + * 用于对网格进行液化变形操作 + */ +public class LiquifyTool extends Tool { + private static final Logger logger = LoggerFactory.getLogger(LiquifyTool.class); + + private ModelPart liquifyTargetPart = null; + private Mesh2D liquifyTargetMesh = null; + + private float liquifyBrushSize = 50.0f; + private float liquifyBrushStrength = 2.0f; + private ModelPart.LiquifyMode currentLiquifyMode = ModelPart.LiquifyMode.PUSH; + + private boolean renderEnabled = false; + + public LiquifyTool(ModelRenderPanel renderPanel) { + super(renderPanel, "液化工具", "对网格进行液化变形操作"); + } + + // ================== 启动和关闭方法 ================== + + /** + * 启动液化工具渲染 + * 启用液化覆盖层和顶点渲染 + */ + public void startLiquifyRendering() { + if (associatedRanderTools == null) { + logger.warn("液化渲染器未初始化"); + return; + } + + // 启用渲染算法 + enableRenderingAlgorithms(); + renderEnabled = true; + + logger.info("启动液化工具渲染"); + } + + /** + * 关闭液化工具渲染 + * 禁用所有液化相关的渲染效果 + */ + public void stopLiquifyRendering() { + if (associatedRanderTools == null) { + return; + } + + // 禁用渲染算法 + disableRenderingAlgorithms(); + renderEnabled = false; + + logger.info("关闭液化工具渲染"); + } + + /** + * 启用渲染算法 + */ + private void enableRenderingAlgorithms() { + if (associatedRanderTools != null) { + // 使用正确的方法设置算法状态 + associatedRanderTools.setAlgorithmEnabled("showLiquifyOverlay", true); + + // 根据当前状态决定是否显示顶点 + boolean showVertices = renderPanel.getKeyboardManager().getIsCtrlPressed(); + associatedRanderTools.setAlgorithmEnabled("isRenderVertices", showVertices); + } + } + + + /** + * 禁用渲染算法 + */ + private void disableRenderingAlgorithms() { + if (associatedRanderTools != null) { + associatedRanderTools.setAlgorithmEnabled("showLiquifyOverlay", false); + associatedRanderTools.setAlgorithmEnabled("isRenderVertices", false); + } + } + + + /** + * 切换顶点显示状态 + */ + public void toggleVertexRendering() { + if (associatedRanderTools != null && renderEnabled) { + boolean currentState = associatedRanderTools.isAlgorithmEnabled("isRenderVertices"); + associatedRanderTools.setAlgorithmEnabled("isRenderVertices", !currentState); + logger.info("切换顶点显示状态: {}", !currentState); + } + } + + /** + * 设置顶点显示状态 + */ + public void setVertexRendering(boolean enabled) { + if (associatedRanderTools != null && renderEnabled) { + associatedRanderTools.setAlgorithmEnabled("isRenderVertices", enabled); + logger.info("设置顶点显示状态: {}", enabled); + } + } + + // ================== 工具生命周期方法 ================== + + @Override + public void activate() { + if (isActive) return; + + isActive = true; + + // 尝试获取选中的网格作为液化目标 + if (!renderPanel.getSelectedMeshes().isEmpty()) { + liquifyTargetMesh = renderPanel.getSelectedMesh(); + liquifyTargetPart = renderPanel.findPartByMesh(liquifyTargetMesh); + } else { + // 如果没有选中的网格,尝试获取第一个可见网格 + liquifyTargetMesh = findFirstVisibleMesh(); + liquifyTargetPart = renderPanel.findPartByMesh(liquifyTargetMesh); + } + + if (liquifyTargetPart != null) { + liquifyTargetPart.setStartLiquefy(true); + // 启动渲染 + startLiquifyRendering(); + logger.info("激活液化工具: {}", liquifyTargetMesh != null ? liquifyTargetMesh.getName() : "null"); + } else { + logger.warn("没有找到可用的网格用于液化"); + } + } + + @Override + public void deactivate() { + if (!isActive) return; + + isActive = false; + + // 停止渲染 + stopLiquifyRendering(); + + if (liquifyTargetPart != null) { + liquifyTargetPart.setStartLiquefy(false); + } + liquifyTargetMesh = null; + liquifyTargetPart = null; + + logger.info("停用液化工具"); + } + + // ================== 事件处理方法 ================== + + @Override + public void onMousePressed(MouseEvent e, float modelX, float modelY) { + if (!isActive || liquifyTargetPart == null) return; + + // 液化模式下,左键按下直接开始液化操作 + if (e.getButton() == MouseEvent.BUTTON1) { + applyLiquifyEffect(modelX, modelY); + } + + // 右键可以切换顶点显示 + if (e.getButton() == MouseEvent.BUTTON3) { + toggleVertexRendering(); + renderPanel.repaint(); + } + } + + @Override + public void onMouseReleased(MouseEvent e, float modelX, float modelY) { + // 液化工具不需要特殊的释放处理 + } + + @Override + public void onMouseDragged(MouseEvent e, float modelX, float modelY) { + if (!isActive || liquifyTargetPart == null) return; + + // 液化模式下拖拽时连续应用液化效果 + if (e.getButton() == MouseEvent.BUTTON1) { + applyLiquifyEffect(modelX, modelY); + } + } + + @Override + public void onMouseMoved(MouseEvent e, float modelX, float modelY) { + // 液化工具不需要特殊的移动处理 + } + + @Override + public void onMouseClicked(MouseEvent e, float modelX, float modelY) { + // 单单击已在 pressed 中处理 + } + + @Override + public void onMouseDoubleClicked(MouseEvent e, float modelX, float modelY) { + if (!isActive) return; + + // 双击空白处退出液化模式 + if (liquifyTargetPart == null || !isOverTargetMesh(modelX, modelY)) { + // 切换到选择工具 + renderPanel.getToolManagement().switchToDefaultTool(); + } + } + + @Override + public Cursor getToolCursor() { + return createLiquifyCursor(); + } + + // ================== 工具特定方法 ================== + + /** + * 应用液化效果 + */ + 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(); + } + + /** + * 检查是否在目标网格上 + */ + private boolean isOverTargetMesh(float modelX, float modelY) { + if (liquifyTargetMesh == null) return false; + + // 更新边界框 + liquifyTargetMesh.updateBounds(); + return liquifyTargetMesh.containsPoint(modelX, modelY); + } + + /** + * 查找第一个可见的网格 + */ + private Mesh2D findFirstVisibleMesh() { + Model2D model = renderPanel.getModel(); + if (model == null) return null; + + java.util.List parts = model.getParts(); + if (parts == null || parts.isEmpty()) return null; + + for (ModelPart part : parts) { + if (part != null && part.isVisible()) { + java.util.List meshes = part.getMeshes(); + if (meshes != null && !meshes.isEmpty()) { + for (Mesh2D mesh : meshes) { + if (mesh != null && mesh.isVisible()) { + return mesh; + } + } + } + } + } + return null; + } + + /** + * 创建液化模式光标 + */ + private Cursor createLiquifyCursor() { + // 创建自定义液化光标(圆圈) + int size = 32; + BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = cursorImg.createGraphics(); + + // 设置抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 绘制透明背景 + g2d.setColor(new Color(0, 0, 0, 0)); + g2d.fillRect(0, 0, size, size); + + // 绘制圆圈 + int center = size / 2; + int radius = (int) (liquifyBrushSize * 0.1f); // 根据画笔大小缩放光标 + + // 外圈 + g2d.setColor(Color.RED); + g2d.setStroke(new BasicStroke(2f)); + g2d.drawOval(center - radius, center - radius, radius * 2, radius * 2); + + // 内圈 + g2d.setColor(new Color(255, 100, 100, 150)); + g2d.setStroke(new BasicStroke(1f)); + g2d.drawOval(center - radius / 2, center - radius / 2, radius, radius); + + // 中心点 + g2d.setColor(Color.RED); + g2d.fillOval(center - 2, center - 2, 4, 4); + + g2d.dispose(); + + return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "LiquifyCursor"); + } + + // ================== 配置方法 ================== + + /** + * 设置液化画笔大小 + */ + public void setLiquifyBrushSize(float size) { + this.liquifyBrushSize = Math.max(1.0f, Math.min(500.0f, size)); + } + + /** + * 设置液化画笔强度 + */ + public void setLiquifyBrushStrength(float strength) { + this.liquifyBrushStrength = Math.max(0.0f, Math.min(2.0f, strength)); + } + + /** + * 设置液化模式 + */ + public void setLiquifyMode(ModelPart.LiquifyMode mode) { + this.currentLiquifyMode = mode; + } + + // ================== 获取工具状态 ================== + + public ModelPart getLiquifyTargetPart() { + return liquifyTargetPart; + } + + public Mesh2D getLiquifyTargetMesh() { + return liquifyTargetMesh; + } + + public float getLiquifyBrushSize() { + return liquifyBrushSize; + } + + public float getLiquifyBrushStrength() { + return liquifyBrushStrength; + } + + public ModelPart.LiquifyMode getCurrentLiquifyMode() { + return currentLiquifyMode; + } + + /** + * 获取渲染器实例 + */ + public LiquifyTargetPartRander getAssociatedRanderTools() { + return (LiquifyTargetPartRander) associatedRanderTools; + } + + /** + * 检查渲染是否启用 + */ + public boolean isRenderEnabled() { + return renderEnabled; + } + + /** + * 检查是否显示顶点 + */ + public boolean isVertexRenderingEnabled() { + return associatedRanderTools != null && + associatedRanderTools.getAlgorithmEnabled().getOrDefault("isRenderVertices", false); + } + + /** + * 检查是否显示液化覆盖层 + */ + public boolean isLiquifyOverlayEnabled() { + return associatedRanderTools != null && + associatedRanderTools.getAlgorithmEnabled().getOrDefault("showLiquifyOverlay", false); + } + + @Override + public void dispose() { + // 清理资源 + stopLiquifyRendering(); + associatedRanderTools = null; + super.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/PuppetDeformationTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/PuppetDeformationTool.java new file mode 100644 index 0000000..2c0bbc9 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/PuppetDeformationTool.java @@ -0,0 +1,297 @@ +package com.chuangzhou.vivid2D.render.awt.tools; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.model.ModelPart; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.model.util.PuppetPin; +import com.chuangzhou.vivid2D.render.model.util.BoundingBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; + +/** + * 木偶变形工具 + * 用于通过控制点对网格进行变形 + */ +public class PuppetDeformationTool extends Tool { + private static final Logger logger = LoggerFactory.getLogger(PuppetDeformationTool.class); + + private Mesh2D targetMesh = null; + private PuppetPin selectedPin = null; + private PuppetPin hoveredPin = null; + + private static final float PIN_TOLERANCE = 8.0f; + private ModelRenderPanel.DragMode currentDragMode = ModelRenderPanel.DragMode.NONE; + private float dragStartX, dragStartY; + + public PuppetDeformationTool(ModelRenderPanel renderPanel) { + super(renderPanel, "木偶变形工具", "通过控制点对网格进行变形操作"); + } + + @Override + public void activate() { + if (isActive) return; + + // 检查是否有选中的网格 + if (renderPanel.getSelectedMeshes().isEmpty()) { + logger.warn("请先选择一个网格以进入木偶变形工具模式"); + return; + } + + isActive = true; + targetMesh = renderPanel.getSelectedMesh(); + + if (targetMesh != null) { + // 显示木偶控制点 + associatedRanderTools.setAlgorithmEnabled("showPuppetPins", true); + targetMesh.setShowPuppetPins(true); + + // 如果没有木偶控制点,创建默认的四个角点 + if (targetMesh.getPuppetPinCount() == 0) { + createDefaultPuppetPins(); + } + + // 预计算权重 + targetMesh.precomputeAllPuppetWeights(); + } + + logger.info("激活木偶变形工具: {}", targetMesh != null ? targetMesh.getName() : "null"); + } + + @Override + public void deactivate() { + if (!isActive) return; + + isActive = false; + if (targetMesh != null) { + associatedRanderTools.setAlgorithmEnabled("showPuppetPins", false); + targetMesh.setShowPuppetPins(false); + } + targetMesh = null; + selectedPin = null; + hoveredPin = null; + + logger.info("停用木偶变形工具"); + } + + @Override + public void onMousePressed(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 选择木偶控制点 + PuppetPin clickedPin = findPuppetPinAtPosition(modelX, modelY); + if (clickedPin != null) { + targetMesh.setSelectedPuppetPin(clickedPin); + selectedPin = clickedPin; + + // 开始拖拽 + currentDragMode = ModelRenderPanel.DragMode.MOVE_PUPPET_PIN; + dragStartX = modelX; + dragStartY = modelY; + + logger.debug("开始移动木偶控制点: ID={}", clickedPin.getId()); + } else { + // 点击空白处,取消选择 + targetMesh.setSelectedPuppetPin(null); + selectedPin = null; + } + } + + @Override + public void onMouseReleased(MouseEvent e, float modelX, float modelY) { + if (!isActive) return; + + currentDragMode = ModelRenderPanel.DragMode.NONE; + } + + @Override + public void onMouseDragged(MouseEvent e, float modelX, float modelY) { + if (!isActive || selectedPin == null) return; + + if (currentDragMode == ModelRenderPanel.DragMode.MOVE_PUPPET_PIN) { + selectedPin.setPosition(modelX, modelY); + dragStartX = modelX; + dragStartY = modelY; + targetMesh.updateVerticesFromPuppetPins(); + targetMesh.markDirty(); + renderPanel.repaint(); + } + } + + @Override + public void onMouseMoved(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 更新悬停的木偶控制点 + PuppetPin newHoveredPin = findPuppetPinAtPosition(modelX, modelY); + + if (newHoveredPin != hoveredPin) { + hoveredPin = newHoveredPin; + } + } + + @Override + public void onMouseClicked(MouseEvent e, float modelX, float modelY) { + // 单单击不需要特殊处理 + } + + @Override + public void onMouseDoubleClicked(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 检查是否双击了木偶控制点 + PuppetPin clickedPin = findPuppetPinAtPosition(modelX, modelY); + if (clickedPin != null) { + // 双击木偶控制点:删除该控制点 + deletePuppetPin(clickedPin); + } else { + // 双击空白处:创建新的木偶控制点 + createPuppetPinAt(modelX, modelY); + } + } + + @Override + public Cursor getToolCursor() { + return createPuppetCursor(); + } + + // ================== 工具特定方法 ================== + + /** + * 创建默认的四个角点木偶控制点 + */ + private void createDefaultPuppetPins() { + if (targetMesh == null) return; + + BoundingBox bounds = targetMesh.getBounds(); + if (bounds == null || !bounds.isValid()) return; + + float minX = bounds.getMinX(); + float minY = bounds.getMinY(); + float maxX = bounds.getMaxX(); + float maxY = bounds.getMaxY(); + + // 创建四个角点 + targetMesh.addPuppetPin(minX, minY, 0.0f, 1.0f); // 左下 + targetMesh.addPuppetPin(maxX, minY, 1.0f, 1.0f); // 右下 + targetMesh.addPuppetPin(maxX, maxY, 1.0f, 0.0f); // 右上 + targetMesh.addPuppetPin(minX, maxY, 0.0f, 0.0f); // 左上 + + logger.debug("为网格 {} 创建了4个默认木偶控制点", targetMesh.getName()); + } + + /** + * 在指定位置创建木偶控制点 + */ + private void createPuppetPinAt(float x, float y) { + if (targetMesh == null) return; + + // 计算UV坐标(基于边界框) + BoundingBox bounds = targetMesh.getBounds(); + if (bounds == null || !bounds.isValid()) return; + + float u = (x - bounds.getMinX()) / bounds.getWidth(); + float v = (y - bounds.getMinY()) / bounds.getHeight(); + + // 限制UV在0-1范围内 + u = Math.max(0.0f, Math.min(1.0f, u)); + v = Math.max(0.0f, Math.min(1.0f, v)); + + PuppetPin newPin = targetMesh.addPuppetPin(x, y, u, v); + logger.info("创建木偶控制点: ID={}, 位置({}, {}), UV({}, {})", + newPin.getId(), x, y, u, v); + + renderPanel.repaint(); + } + + /** + * 删除木偶控制点 + */ + private void deletePuppetPin(PuppetPin pin) { + if (targetMesh == null || pin == null) return; + + boolean removed = targetMesh.removePuppetPin(pin); + if (removed) { + if (selectedPin == pin) { + selectedPin = null; + } + if (hoveredPin == pin) { + hoveredPin = null; + } + logger.info("删除木偶控制点: ID={}", pin.getId()); + } + + renderPanel.repaint(); + } + + /** + * 在指定位置查找木偶控制点 + */ + private PuppetPin findPuppetPinAtPosition(float x, float y) { + if (targetMesh == null) return null; + + float tolerance = PIN_TOLERANCE / calculateScaleFactor(); + return targetMesh.selectPuppetPinAt(x, y, tolerance); + } + + /** + * 计算当前缩放因子 + */ + private float calculateScaleFactor() { + return renderPanel.getCameraManagement().calculateScaleFactor(); + } + + /** + * 创建木偶工具光标 + */ + private Cursor createPuppetCursor() { + int size = 32; + BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = cursorImg.createGraphics(); + + // 设置抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 绘制透明背景 + g2d.setColor(new Color(0, 0, 0, 0)); + g2d.fillRect(0, 0, size, size); + + // 绘制木偶图标 + int center = size / 2; + + // 外圈(蓝色) + g2d.setColor(Color.BLUE); + g2d.setStroke(new BasicStroke(2f)); + g2d.drawOval(center - 6, center - 6, 12, 12); + + // 内圈 + g2d.setColor(new Color(0, 0, 200, 150)); + g2d.setStroke(new BasicStroke(1f)); + g2d.drawOval(center - 3, center - 3, 6, 6); + + // 中心点 + g2d.setColor(Color.BLUE); + g2d.fillOval(center - 1, center - 1, 2, 2); + + g2d.dispose(); + + return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "PuppetCursor"); + } + + // ================== 获取工具状态 ================== + + public Mesh2D getTargetMesh() { + return targetMesh; + } + + public PuppetPin getSelectedPin() { + return selectedPin; + } + + public PuppetPin getHoveredPin() { + return hoveredPin; + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java new file mode 100644 index 0000000..be1c59b --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java @@ -0,0 +1,1057 @@ +package com.chuangzhou.vivid2D.render.awt.tools; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.model.Model2D; +import com.chuangzhou.vivid2D.render.model.ModelPart; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.model.util.BoundingBox; +import org.joml.Vector2f; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 选择工具(默认工具) + * 用于选择和管理网格 + */ +public class SelectionTool extends Tool { + private static final Logger logger = LoggerFactory.getLogger(SelectionTool.class); + // 选择工具专用字段 + private volatile Mesh2D hoveredMesh = null; + private final Set selectedMeshes = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private volatile Mesh2D lastSelectedMesh = null; + private volatile ModelPart draggedPart = null; + private volatile float dragStartX, dragStartY; + + // 拖拽相关字段 + private volatile ModelRenderPanel.DragMode currentDragMode = ModelRenderPanel.DragMode.NONE; + private volatile boolean shiftDuringDrag = false; + private volatile float rotationStartAngle = 0.0f; + private volatile float resizeStartWidth, resizeStartHeight; + + // 状态记录 + private final Map dragStartPositions = new HashMap<>(); + private final Map dragStartScales = new HashMap<>(); + private final Map dragStartRotations = new HashMap<>(); + private final Map dragStartPivots = new HashMap<>(); + + public SelectionTool(ModelRenderPanel renderPanel) { + super(renderPanel, "选择工具", "选择和操作网格对象"); + } + + @Override + public void activate() { + isActive = true; + // 激活时恢复默认光标 + renderPanel.setCursor(Cursor.getDefaultCursor()); + } + + @Override + public void deactivate() { + isActive = false; + // 清理选择状态 + clearSelectedMeshes(); + } + + @Override + public void onMousePressed(MouseEvent e, float modelX, float modelY) { + if (!renderPanel.getGlContextManager().isContextInitialized()) return; + + shiftDuringDrag = e.isShiftDown(); + + renderPanel.getGlContextManager().executeInGLContext(() -> { + try { + // 清空之前的状态记录 + dragStartPositions.clear(); + dragStartScales.clear(); + dragStartRotations.clear(); + dragStartPivots.clear(); + + // 首先检查是否点击了选择框的调整手柄 + // 多选时只对最后一个选中的网格进行操作 + Mesh2D targetMeshForHandle = selectedMeshes.isEmpty() ? null : lastSelectedMesh; + ModelRenderPanel.DragMode dragMode = targetMeshForHandle != null ? + checkResizeHandleHit(modelX, modelY, targetMeshForHandle) : ModelRenderPanel.DragMode.NONE; + + if (dragMode == ModelRenderPanel.DragMode.ROTATE) { + // 开始旋转 - 记录初始旋转状态 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + dragStartRotations.put(part, part.getRotation()); + } + currentDragMode = ModelRenderPanel.DragMode.ROTATE; + dragStartX = modelX; + dragStartY = modelY; + + // 获取边界框和中心点 + BoundingBox bounds = targetMeshForHandle.getBounds(); + renderPanel.getCameraManagement().getRotationCenter().set( + (bounds.getMinX() + bounds.getMaxX()) / 2.0f, + (bounds.getMinY() + bounds.getMaxY()) / 2.0f + ); + + // 计算初始角度 + rotationStartAngle = (float) Math.atan2( + dragStartY - renderPanel.getCameraManagement().getRotationCenter().y, + dragStartX - renderPanel.getCameraManagement().getRotationCenter().x + ); + + } else if (dragMode == ModelRenderPanel.DragMode.MOVE_PIVOT && targetMeshForHandle != null) { + // 开始移动中心点 - 记录初始中心点状态 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + dragStartPivots.put(part, new Vector2f(part.getPivot())); + } + currentDragMode = ModelRenderPanel.DragMode.MOVE_PIVOT; + dragStartX = modelX; + dragStartY = modelY; + + // 记录初始中心点位置 + BoundingBox bounds = targetMeshForHandle.getBounds(); + renderPanel.getCameraManagement().getRotationCenter().set( + (bounds.getMinX() + bounds.getMaxX()) / 2.0f, + (bounds.getMinY() + bounds.getMaxY()) / 2.0f + ); + } else if (dragMode != ModelRenderPanel.DragMode.NONE) { + // 开始调整大小 - 记录初始缩放状态 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + dragStartScales.put(part, new Vector2f(part.getScale())); + } + currentDragMode = dragMode; + dragStartX = modelX; + dragStartY = modelY; + + BoundingBox bounds = targetMeshForHandle.getBounds(); + resizeStartWidth = bounds.getWidth(); + resizeStartHeight = bounds.getHeight(); + + } else { + // 检查是否点击了网格(移动操作) + Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY); + + if (clickedMesh != null) { + handleMultiSelect(clickedMesh, e.isShiftDown(), e.isControlDown()); + + // 记录初始位置状态 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + dragStartPositions.put(part, new Vector2f(part.getPosition())); + } + + // 设置拖拽目标 + draggedPart = findPartByMesh(clickedMesh); + dragStartX = modelX; + dragStartY = modelY; + currentDragMode = ModelRenderPanel.DragMode.MOVE; + } else { + // 点击空白区域 + if (!e.isControlDown() && !e.isShiftDown()) { + clearSelectedMeshes(); + } + } + } + + // 更新拖拽过程中的光标 + updateCursorForDragMode(currentDragMode); + + } catch (Exception ex) { + logger.error("选择工具处理鼠标按下时出错", ex); + } + }); + } + + @Override + public void onMouseReleased(MouseEvent e, float modelX, float modelY) { + if (currentDragMode != ModelRenderPanel.DragMode.NONE) { + // 记录操作历史 + try { + List selectedParts = getSelectedParts(); + switch (currentDragMode) { + case MOVE: + if (!dragStartPositions.isEmpty() && !selectedParts.isEmpty()) { + renderPanel.getStatusRecordManagement().recordDragEnd( + selectedParts, new HashMap<>(dragStartPositions) + ); + } + break; + case ROTATE: + if (!dragStartRotations.isEmpty() && !selectedParts.isEmpty()) { + renderPanel.getStatusRecordManagement().recordRotateEnd( + selectedParts, new HashMap<>(dragStartRotations) + ); + } + break; + case MOVE_PIVOT: + if (!dragStartPivots.isEmpty() && !selectedParts.isEmpty()) { + renderPanel.getStatusRecordManagement().recordMovePivotEnd( + selectedParts, new HashMap<>(dragStartPivots) + ); + } + break; + default: + if (!dragStartScales.isEmpty() && !selectedParts.isEmpty()) { + renderPanel.getStatusRecordManagement().recordResizeEnd( + selectedParts, new HashMap<>(dragStartScales) + ); + } + break; + } + } catch (Exception ex) { + logger.error("选择工具记录操作历史时出错", ex); + } + } + + // 重置状态 + draggedPart = null; + currentDragMode = ModelRenderPanel.DragMode.NONE; + shiftDuringDrag = false; + + // 清空状态记录 + dragStartPositions.clear(); + dragStartScales.clear(); + dragStartRotations.clear(); + dragStartPivots.clear(); + + // 恢复悬停状态的光标 + updateCursorForHoverState(modelX, modelY); + } + + @Override + public void onMouseDragged(MouseEvent e, float modelX, float modelY) { + if (currentDragMode == ModelRenderPanel.DragMode.NONE) return; + + renderPanel.getGlContextManager().executeInGLContext(() -> { + try { + switch (currentDragMode) { + case MOVE: + handleMoveDrag(modelX, modelY); + break; + case ROTATE: + handleRotateDrag(modelX, modelY); + break; + case MOVE_PIVOT: + handleMovePivotDrag(modelX, modelY); + break; + default: + handleResizeDrag(modelX, modelY); + break; + } + + } catch (Exception ex) { + logger.error("选择工具处理鼠标拖拽时出错", ex); + } + }); + } + + @Override + public void onMouseMoved(MouseEvent e, float modelX, float modelY) { + if (!renderPanel.getGlContextManager().isContextInitialized()) return; + + renderPanel.getGlContextManager().executeInGLContext(() -> { + try { + // 检测悬停的网格 + Mesh2D newHoveredMesh = findMeshAtPosition(modelX, modelY); + + // 如果悬停网格发生变化,更新状态 + if (newHoveredMesh != hoveredMesh) { + if (hoveredMesh != null) { + hoveredMesh.setSuspension(false); + hoveredMesh.setRenderVertices(false); + } + + hoveredMesh = newHoveredMesh; + if (hoveredMesh != null) { + hoveredMesh.setSuspension(true); + } + } + + updateCursorForHoverState(modelX, modelY, newHoveredMesh); + + } catch (Exception ex) { + logger.error("选择工具处理鼠标移动时出错", ex); + } + }); + } + + @Override + public void onMouseClicked(MouseEvent e, float modelX, float modelY) { + // 选择工具的点击逻辑已在 onMousePressed 中处理 + } + + @Override + public void onMouseDoubleClicked(MouseEvent e, float modelX, float modelY) { + // 选择工具的双击逻辑 - 进入液化模式 + renderPanel.getGlContextManager().executeInGLContext(() -> { + try { + // 检测双击的网格 + Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY); + if (clickedMesh != null) { + enterLiquifyMode(clickedMesh); + } + } catch (Exception ex) { + logger.error("选择工具处理双击时出错", ex); + } + }); + } + + @Override + public Cursor getToolCursor() { + return Cursor.getDefaultCursor(); + } + + // ================== 选择工具专用方法 ================== + + /** + * 处理移动拖拽 + */ + private void handleMoveDrag(float modelX, float modelY) { + if (selectedMeshes.isEmpty()) return; + + float deltaX = modelX - dragStartX; + float deltaY = modelY - dragStartY; + + // 移动所有选中的部件 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + Vector2f pos = part.getPosition(); + part.setPosition(pos.x + deltaX, pos.y + deltaY); + + // 同步移动该部件下的所有网格的二级顶点 + syncSecondaryVerticesForPart(part, deltaX, deltaY); + } + + // 更新拖拽起始位置 + dragStartX = modelX; + dragStartY = modelY; + } + + /** + * 处理旋转拖拽 + */ + private void handleRotateDrag(float modelX, float modelY) { + if (lastSelectedMesh == null) return; + + // 计算当前角度 + float currentAngle = (float) Math.atan2( + modelY - renderPanel.getCameraManagement().getRotationCenter().y, + modelX - renderPanel.getCameraManagement().getRotationCenter().x + ); + + // 计算旋转增量 + float deltaAngle = currentAngle - rotationStartAngle; + + // 如果按住Shift键,以15度为步长进行约束旋转 + if (renderPanel.getKeyboardManager().getIsShiftPressed() || shiftDuringDrag) { + float constraintStep = (float) (Math.PI / 12); // 15度 + deltaAngle = Math.round(deltaAngle / constraintStep) * constraintStep; + } + + // 应用旋转到所有选中的部件 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + part.rotate(deltaAngle); + } + + // 更新旋转起始角度 + rotationStartAngle = currentAngle; + } + + /** + * 处理移动中心点拖拽 + */ + private void handleMovePivotDrag(float modelX, float modelY) { + if (lastSelectedMesh == null) return; + + float deltaX = modelX - dragStartX; + float deltaY = modelY - dragStartY; + + // 只移动主选中网格的中心点 + ModelPart selectedPart = findPartByMesh(lastSelectedMesh); + if (selectedPart == null) return; + + Vector2f currentPivot = selectedPart.getPivot(); + float newPivotX = currentPivot.x + deltaX; + float newPivotY = currentPivot.y + deltaY; + + if (selectedPart.setPivot(newPivotX, newPivotY)) { + dragStartX = modelX; + dragStartY = modelY; + renderPanel.getCameraManagement().getRotationCenter().set(newPivotX, newPivotY); + } + } + + /** + * 处理调整大小拖拽 + */ + private void handleResizeDrag(float modelX, float modelY) { + if (lastSelectedMesh == null) return; + + ModelPart selectedPart = findPartByMesh(lastSelectedMesh); + if (selectedPart == null) return; + + float deltaX = modelX - dragStartX; + float deltaY = modelY - dragStartY; + + float relScaleX = 1.0f; + float relScaleY = 1.0f; + + // 根据拖拽模式计算相对缩放比例 + switch (currentDragMode) { + case RESIZE_LEFT: + relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth); + break; + case RESIZE_RIGHT: + relScaleX = (resizeStartWidth + deltaX) / Math.max(1e-6f, resizeStartWidth); + break; + case RESIZE_TOP: + relScaleY = (resizeStartHeight - deltaY) / Math.max(1e-6f, resizeStartHeight); + break; + case RESIZE_BOTTOM: + relScaleY = (resizeStartHeight + deltaY) / Math.max(1e-6f, resizeStartHeight); + break; + case RESIZE_TOP_LEFT: + relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth); + relScaleY = (resizeStartHeight - deltaY) / Math.max(1e-6f, resizeStartHeight); + break; + case RESIZE_TOP_RIGHT: + relScaleX = (resizeStartWidth + deltaX) / Math.max(1e-6f, resizeStartWidth); + relScaleY = (resizeStartHeight - deltaY) / Math.max(1e-6f, resizeStartHeight); + break; + case RESIZE_BOTTOM_LEFT: + relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth); + relScaleY = (resizeStartHeight + deltaY) / Math.max(1e-6f, resizeStartHeight); + break; + case RESIZE_BOTTOM_RIGHT: + relScaleX = (resizeStartWidth + deltaX) / Math.max(1e-6f, resizeStartWidth); + relScaleY = (resizeStartHeight + deltaY) / Math.max(1e-6f, resizeStartHeight); + break; + } + + // 如果按住Shift键,等比例缩放 + if (renderPanel.getKeyboardManager().getIsShiftPressed() || shiftDuringDrag) { + float uniform = (relScaleX + relScaleY) * 0.5f; + relScaleX = uniform; + relScaleY = uniform; + } + + // 应用缩放到所有选中的部件 + List selectedParts = getSelectedParts(); + for (ModelPart part : selectedParts) { + Vector2f currentScale = part.getScale(); + part.setScale(currentScale.x * relScaleX, currentScale.y * relScaleY); + + // 同步缩放该部件下的所有网格的二级顶点 + syncSecondaryVerticesScaleForPart(part, relScaleX, relScaleY); + } + + // 更新拖拽起始位置和初始尺寸 + dragStartX = modelX; + dragStartY = modelY; + resizeStartWidth *= relScaleX; + resizeStartHeight *= relScaleY; + } + + /** + * 同步部件下所有网格的二级顶点位置 + */ + private void syncSecondaryVerticesForPart(ModelPart part, float deltaX, float deltaY) { + if (part == null) return; + + List meshes = part.getMeshes(); + if (meshes == null) return; + + for (Mesh2D mesh : meshes) { + if (mesh != null && mesh.getSecondaryVertexCount() > 0) { + mesh.moveSecondaryVertices(deltaX, deltaY); + } + } + + // 递归处理子部件 + for (ModelPart child : part.getChildren()) { + syncSecondaryVerticesForPart(child, deltaX, deltaY); + } + } + + /** + * 同步部件下所有网格的二级顶点缩放 + */ + private void syncSecondaryVerticesScaleForPart(ModelPart part, float scaleX, float scaleY) { + if (part == null) return; + + List meshes = part.getMeshes(); + if (meshes == null) return; + + for (Mesh2D mesh : meshes) { + if (mesh != null && mesh.getSecondaryVertexCount() > 0) { + mesh.syncSecondaryVerticesToBounds(); + } + } + + // 递归处理子部件 + for (ModelPart child : part.getChildren()) { + syncSecondaryVerticesScaleForPart(child, scaleX, scaleY); + } + } + + /** + * 处理多选逻辑 + */ + private void handleMultiSelect(Mesh2D clickedMesh, boolean isShiftDown, boolean isCtrlDown) { + if (isCtrlDown) { + if (selectedMeshes.contains(clickedMesh)) { + removeSelectedMesh(clickedMesh); + } else { + addSelectedMesh(clickedMesh); + } + } else if (isShiftDown && lastSelectedMesh != null) { + selectRange(lastSelectedMesh, clickedMesh); + } else if (!isInMultiSelection()) { + if (!selectedMeshes.contains(clickedMesh)) { + setSelectedMesh(clickedMesh); + } + } + } + + /** + * 检查是否在选中的网格上 + */ + private ModelRenderPanel.DragMode checkResizeHandleHit(float modelX, float modelY, Mesh2D targetMesh) { + if (targetMesh == null) { + return ModelRenderPanel.DragMode.NONE; + } + + BoundingBox bounds; + Vector2f center; + + // 多选状态下使用多选边界框 + if (targetMesh.isInMultiSelection()) { + bounds = targetMesh.getMultiSelectionBounds(); + center = bounds.getCenter(); + } else { + bounds = targetMesh.getBounds(); + center = targetMesh.getPivot(); + } + + float scaleFactor = renderPanel.getCameraManagement().calculateScaleFactor(); + float borderThickness = ModelRenderPanel.BORDER_THICKNESS / scaleFactor; + float cornerSize = ModelRenderPanel.CORNER_SIZE / scaleFactor; + + float minX = bounds.getMinX(); + float minY = bounds.getMinY(); + float maxX = bounds.getMaxX(); + float maxY = bounds.getMaxY(); + + ModelRenderPanel.DragMode result = ModelRenderPanel.DragMode.NONE; + + // 检查中心点 + if (isPointInCenterHandle(modelX, modelY, center.x, center.y, cornerSize)) { + result = ModelRenderPanel.DragMode.MOVE_PIVOT; + } + + // 检查旋转手柄 + if (result == ModelRenderPanel.DragMode.NONE && + isPointInRotationHandle(modelX, modelY, center.x, center.y, minY, cornerSize)) { + result = ModelRenderPanel.DragMode.ROTATE; + } + + // 扩展边界以包含调整手柄区域 + float expandedMinX = minX - borderThickness; + float expandedMinY = minY - borderThickness; + float expandedMaxX = maxX + borderThickness; + float expandedMaxY = maxY + borderThickness; + + if (result == ModelRenderPanel.DragMode.NONE) { + if (modelX < expandedMinX || modelX > expandedMaxX || + modelY < expandedMinY || modelY > expandedMaxY) { + return ModelRenderPanel.DragMode.NONE; + } + } + + // 检查角点 + if (result == ModelRenderPanel.DragMode.NONE && isPointInCorner(modelX, modelY, minX, minY, cornerSize)) { + result = ModelRenderPanel.DragMode.RESIZE_TOP_LEFT; + } + if (result == ModelRenderPanel.DragMode.NONE && isPointInCorner(modelX, modelY, maxX, minY, cornerSize)) { + result = ModelRenderPanel.DragMode.RESIZE_TOP_RIGHT; + } + if (result == ModelRenderPanel.DragMode.NONE && isPointInCorner(modelX, modelY, minX, maxY, cornerSize)) { + result = ModelRenderPanel.DragMode.RESIZE_BOTTOM_LEFT; + } + if (result == ModelRenderPanel.DragMode.NONE && isPointInCorner(modelX, modelY, maxX, maxY, cornerSize)) { + result = ModelRenderPanel.DragMode.RESIZE_BOTTOM_RIGHT; + } + + // 检查边 + if (result == ModelRenderPanel.DragMode.NONE) { + if (modelX >= minX - borderThickness && modelX <= minX + borderThickness) { + result = ModelRenderPanel.DragMode.RESIZE_LEFT; + } else if (modelX >= maxX - borderThickness && modelX <= maxX + borderThickness) { + result = ModelRenderPanel.DragMode.RESIZE_RIGHT; + } else if (modelY >= minY - borderThickness && modelY <= minY + borderThickness) { + result = ModelRenderPanel.DragMode.RESIZE_TOP; + } else if (modelY >= maxY - borderThickness && modelY <= maxY + borderThickness) { + result = ModelRenderPanel.DragMode.RESIZE_BOTTOM; + } + } + + return result; + } + + // 辅助方法:检查点是否在中心点、旋转手柄、角点区域内 + private boolean isPointInCenterHandle(float x, float y, float centerX, float centerY, float handleSize) { + return Math.abs(x - centerX) <= handleSize && Math.abs(y - centerY) <= handleSize; + } + + private boolean isPointInRotationHandle(float x, float y, float centerX, float centerY, float topY, float handleSize) { + float rotationHandleY = topY - renderPanel.getCameraManagement().ROTATION_HANDLE_DISTANCE / + renderPanel.getCameraManagement().calculateScaleFactor(); + float rotationHandleX = centerX; + return Math.abs(x - rotationHandleX) <= handleSize && Math.abs(y - rotationHandleY) <= handleSize; + } + + private boolean isPointInCorner(float x, float y, float cornerX, float cornerY, float cornerSize) { + return x >= cornerX - cornerSize && x <= cornerX + cornerSize && + y >= cornerY - cornerSize && y <= cornerY + cornerSize; + } + + /** + * 根据拖拽模式更新光标 + */ + private void updateCursorForDragMode(ModelRenderPanel.DragMode dragMode) { + Cursor newCursor = getCursorForDragMode(dragMode); + renderPanel.setCursor(newCursor); + } + + /** + * 根据拖拽模式获取对应的光标 + */ + private Cursor getCursorForDragMode(ModelRenderPanel.DragMode dragMode) { + switch (dragMode) { + case MOVE: + return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); + case RESIZE_LEFT: + case RESIZE_RIGHT: + return Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); + case RESIZE_TOP: + case RESIZE_BOTTOM: + return Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); + case RESIZE_TOP_LEFT: + case RESIZE_BOTTOM_RIGHT: + return Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR); + case RESIZE_TOP_RIGHT: + case RESIZE_BOTTOM_LEFT: + return Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR); + case ROTATE: + return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + case MOVE_PIVOT: + return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); + case MOVE_SECONDARY_VERTEX: + case MOVE_PUPPET_PIN: + return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + case NONE: + default: + return Cursor.getDefaultCursor(); + } + } + + /** + * 根据悬停状态更新光标 + */ + private void updateCursorForHoverState(float modelX, float modelY, Mesh2D hoveredMesh) { + // 如果正在拖拽,不更新光标 + if (currentDragMode != ModelRenderPanel.DragMode.NONE) { + return; + } + + Cursor newCursor = Cursor.getDefaultCursor(); + boolean isOverSelection = false; + + // 检查是否在选中的网格上 + if (!selectedMeshes.isEmpty()) { + Mesh2D targetMeshForHandle = lastSelectedMesh; + if (targetMeshForHandle != null) { + ModelRenderPanel.DragMode hoverMode = checkResizeHandleHit(modelX, modelY, targetMeshForHandle); + if (hoverMode != ModelRenderPanel.DragMode.NONE) { + newCursor = getCursorForDragMode(hoverMode); + isOverSelection = true; + } else { + // 检查是否在选中网格的边界框内 + BoundingBox bounds; + if (targetMeshForHandle.isInMultiSelection()) { + bounds = targetMeshForHandle.getMultiSelectionBounds(); + } else { + bounds = targetMeshForHandle.getBounds(); + } + + if (modelX >= bounds.getMinX() && modelX <= bounds.getMaxX() && + modelY >= bounds.getMinY() && modelY <= bounds.getMaxY()) { + newCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); + isOverSelection = true; + } + } + } + } + + // 如果没有在选中的网格上,检查是否在可悬停的网格上 + if (!isOverSelection && hoveredMesh != null) { + newCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + } + + // 更新光标 + renderPanel.setCursor(newCursor); + } + + private void updateCursorForHoverState(float modelX, float modelY) { + updateCursorForHoverState(modelX, modelY, hoveredMesh); + } + + // ================== 选择管理方法 ================== + + /** + * 获取当前选中的网格 + */ + public Mesh2D getSelectedMesh() { + return selectedMeshes.isEmpty() ? null : selectedMeshes.iterator().next(); + } + + /** + * 获取当前选中的所有网格 + */ + public Set getSelectedMeshes() { + return Collections.unmodifiableSet(selectedMeshes); + } + + /** + * 设置选中的网格(单选) + */ + public void setSelectedMesh(Mesh2D mesh) { + renderPanel.getGlContextManager().executeInGLContext(() -> { + // 清除之前选中的所有网格 + for (Mesh2D selectedMesh : selectedMeshes) { + selectedMesh.setSelected(false); + selectedMesh.clearMultiSelection(); + } + selectedMeshes.clear(); + + // 设置新的选中网格 + if (mesh != null) { + mesh.setSelected(true); + selectedMeshes.add(mesh); + lastSelectedMesh = mesh; + updateMultiSelectionInMeshes(); + } else { + lastSelectedMesh = null; + } + }); + } + + /** + * 添加选中的网格(多选) + */ + public void addSelectedMesh(Mesh2D mesh) { + renderPanel.getGlContextManager().executeInGLContext(() -> { + if (mesh != null && !selectedMeshes.contains(mesh)) { + mesh.setSelected(true); + selectedMeshes.add(mesh); + lastSelectedMesh = mesh; + updateMultiSelectionInMeshes(); + } + }); + } + + /** + * 移除选中的网格 + */ + public void removeSelectedMesh(Mesh2D mesh) { + renderPanel.getGlContextManager().executeInGLContext(() -> { + if (mesh != null && selectedMeshes.contains(mesh)) { + mesh.setSelected(false); + selectedMeshes.remove(mesh); + updateMultiSelectionInMeshes(); + + if (mesh == lastSelectedMesh) { + lastSelectedMesh = selectedMeshes.isEmpty() ? null : selectedMeshes.iterator().next(); + } + } + }); + } + + /** + * 清空所有选中的网格 + */ + public void clearSelectedMeshes() { + renderPanel.getGlContextManager().executeInGLContext(() -> { + for (Mesh2D mesh : selectedMeshes) { + mesh.setSelected(false); + mesh.setSuspension(false); + mesh.clearMultiSelection(); + } + selectedMeshes.clear(); + lastSelectedMesh = null; + }); + } + + /** + * 全选所有网格 + */ + public void selectAllMeshes() { + renderPanel.getGlContextManager().executeInGLContext(() -> { + Model2D model = renderPanel.getModel(); + if (model == null) return; + + // 清除之前的选择 + for (Mesh2D mesh : selectedMeshes) { + mesh.setSelected(false); + mesh.clearMultiSelection(); + } + selectedMeshes.clear(); + + // 获取所有网格并选中 + List allMeshes = getAllMeshesFromModel(model); + for (Mesh2D mesh : allMeshes) { + if (mesh.isVisible()) { + mesh.setSelected(true); + selectedMeshes.add(mesh); + } + } + + // 设置最后选中的网格 + if (!selectedMeshes.isEmpty()) { + lastSelectedMesh = selectedMeshes.iterator().next(); + } + + updateMultiSelectionInMeshes(); + }); + } + + /** + * 选择两个网格之间的所有网格 + */ + private void selectRange(Mesh2D fromMesh, Mesh2D toMesh) { + Model2D model = renderPanel.getModel(); + if (model == null) return; + + List allMeshes = getAllMeshesFromModel(model); + int fromIndex = allMeshes.indexOf(fromMesh); + int toIndex = allMeshes.indexOf(toMesh); + + if (fromIndex == -1 || toIndex == -1) { + setSelectedMesh(toMesh); + return; + } + + int start = Math.min(fromIndex, toIndex); + int end = Math.max(fromIndex, toIndex); + + for (int i = start; i <= end; i++) { + Mesh2D mesh = allMeshes.get(i); + if (mesh.isVisible()) { + mesh.setSelected(true); + selectedMeshes.add(mesh); + } + } + + lastSelectedMesh = toMesh; + updateMultiSelectionInMeshes(); + } + + /** + * 更新所有选中网格的多选列表 + */ + private void updateMultiSelectionInMeshes() { + if (selectedMeshes.size() <= 1) { + for (Mesh2D mesh : getAllMeshesFromModel(renderPanel.getModel())) { + mesh.clearMultiSelection(); + } + return; + } + + for (Mesh2D selectedMesh : selectedMeshes) { + selectedMesh.clearMultiSelection(); + for (Mesh2D otherMesh : selectedMeshes) { + if (otherMesh != selectedMesh) { + selectedMesh.addToMultiSelection(otherMesh); + } + } + } + } + + /** + * 检查是否处于多选状态 + */ + private boolean isInMultiSelection() { + return selectedMeshes.size() > 1; + } + + // ================== 辅助方法 ================== + + /** + * 在指定位置查找网格 + */ + private Mesh2D findMeshAtPosition(float modelX, float modelY) { + Model2D model = renderPanel.getModel(); + if (model == null) return null; + + try { + List parts = model.getParts(); + if (parts == null || parts.isEmpty()) return null; + + // 遍历所有部件和网格(从上到下) + for (int i = parts.size() - 1; i >= 0; i--) { + ModelPart part = parts.get(i); + if (part == null || !part.isVisible()) continue; + + List meshes = part.getMeshes(); + if (meshes == null || meshes.isEmpty()) continue; + + for (int m = meshes.size() - 1; m >= 0; m--) { + Mesh2D mesh = meshes.get(m); + if (mesh == null || !mesh.isVisible()) continue; + + if (mesh.isDirty()) { + mesh.updateBounds(); + } + + boolean contains = false; + try { + contains = mesh.containsPoint(modelX, modelY); + } catch (Exception ex) { + logger.warn("mesh.containsPoint 抛出异常: {}", ex.getMessage()); + } + + if (contains) { + return mesh; + } + } + } + return null; + } catch (Exception e) { + logger.error("检测网格时出错", e); + return null; + } + } + + /** + * 通过网格查找对应的 ModelPart + */ + private ModelPart findPartByMesh(Mesh2D mesh) { + Model2D model = renderPanel.getModel(); + if (model == null) return null; + for (ModelPart part : model.getParts()) { + ModelPart found = findPartByMeshRecursive(part, mesh); + if (found != null) { + return found; + } + } + return null; + } + + /** + * 递归查找包含指定网格的部件 + */ + private ModelPart findPartByMeshRecursive(ModelPart part, Mesh2D targetMesh) { + if (part == null || targetMesh == null) return null; + + // 检查当前部件的网格 + for (Mesh2D mesh : part.getMeshes()) { + if (mesh == targetMesh) { + return part; + } + } + + // 递归检查子部件 + for (ModelPart child : part.getChildren()) { + ModelPart found = findPartByMeshRecursive(child, targetMesh); + if (found != null) { + return found; + } + } + + return null; + } + + /** + * 获取模型中的所有网格 + */ + private List getAllMeshesFromModel(Model2D model) { + List allMeshes = new ArrayList<>(); + if (model == null) return allMeshes; + + try { + List parts = model.getParts(); + if (parts == null) return allMeshes; + + for (ModelPart part : parts) { + if (part != null && part.isVisible()) { + addMeshesFromPart(part, allMeshes); + } + } + } catch (Exception e) { + logger.error("获取模型网格时出错", e); + } + + return allMeshes; + } + + /** + * 递归从部件中获取所有网格 + */ + private void addMeshesFromPart(ModelPart part, List meshList) { + if (part == null) return; + + // 添加当前部件的网格 + List meshes = part.getMeshes(); + if (meshes != null) { + for (Mesh2D mesh : meshes) { + if (mesh != null && mesh.isVisible()) { + meshList.add(mesh); + } + } + } + + // 递归处理子部件 + for (ModelPart child : part.getChildren()) { + addMeshesFromPart(child, meshList); + } + } + + /** + * 获取所有选中的部件 + */ + public List getSelectedParts() { + List selectedParts = new ArrayList<>(); + for (Mesh2D mesh : selectedMeshes) { + ModelPart part = findPartByMesh(mesh); + if (part != null && !selectedParts.contains(part)) { + selectedParts.add(part); + } + } + return selectedParts; + } + + /** + * 进入液化模式 + */ + private void enterLiquifyMode(Mesh2D targetMesh) { + if (targetMesh == null) { + logger.warn("无法进入液化模式:目标网格为空"); + return; + } + setSelectedMesh(targetMesh); + renderPanel.switchToLiquifyTool(); + logger.info("选择工具请求进入液化模式: {}", targetMesh.getName()); + } + + /** + * 获取鼠标悬停的网格 + */ + public Mesh2D getHoveredMesh() { + return hoveredMesh; + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/Tool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/Tool.java new file mode 100644 index 0000000..bc5055b --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/Tool.java @@ -0,0 +1,147 @@ +package com.chuangzhou.vivid2D.render.awt.tools; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.model.util.tools.RanderTools; + +import java.awt.*; +import java.awt.event.MouseEvent; + +/** + * 工具抽象基类 + * 所有编辑工具都应继承此类 + */ +public abstract class Tool { + protected ModelRenderPanel renderPanel; + protected String toolName; + protected String toolDescription; + protected boolean isActive = false; + + // 关联的渲染工具对象 + protected RanderTools associatedRanderTools; + + public Tool(ModelRenderPanel renderPanel, String toolName, String toolDescription) { + this.renderPanel = renderPanel; + this.toolName = toolName; + this.toolDescription = toolDescription; + } + + // ================== 生命周期方法 ================== + + /** + * 激活工具 + */ + public abstract void activate(); + + /** + * 停用工具 + */ + public abstract void deactivate(); + + /** + * 工具是否处于激活状态 + */ + public boolean isActive() { + return isActive; + } + + // ================== 事件处理方法 ================== + + /** + * 处理鼠标按下事件 + */ + public abstract void onMousePressed(MouseEvent e, float modelX, float modelY); + + /** + * 处理鼠标释放事件 + */ + public abstract void onMouseReleased(MouseEvent e, float modelX, float modelY); + + /** + * 处理鼠标拖拽事件 + */ + public abstract void onMouseDragged(MouseEvent e, float modelX, float modelY); + + /** + * 处理鼠标移动事件 + */ + public abstract void onMouseMoved(MouseEvent e, float modelX, float modelY); + + /** + * 处理鼠标点击事件 + */ + public abstract void onMouseClicked(MouseEvent e, float modelX, float modelY); + + /** + * 处理鼠标双击事件 + */ + public abstract void onMouseDoubleClicked(MouseEvent e, float modelX, float modelY); + + // ================== 工具状态方法 ================== + + /** + * 获取工具名称 + */ + public String getToolName() { + return toolName; + } + + /** + * 获取工具描述 + */ + public String getToolDescription() { + return toolDescription; + } + + /** + * 获取工具光标 + */ + public abstract Cursor getToolCursor(); + + /** + * 工具是否可用 + */ + public boolean isAvailable() { + return true; + } + + /** + * 清理工具资源 + */ + public void dispose() { + // 子类可重写此方法清理资源 + if (associatedRanderTools != null) { + associatedRanderTools = null; + } + } + + // ================== 新增方法:与RanderToolsManager集成 ================== + + /** + * 设置关联的渲染工具 + * @param randerTools 渲染工具对象 + */ + public void setAssociatedRanderTools(RanderTools randerTools) { + this.associatedRanderTools = randerTools; + } + + /** + * 获取关联的渲染工具 + * @return 关联的渲染工具对象,可能为null + */ + public RanderTools getAssociatedRanderTools() { + return associatedRanderTools; + } + + /** + * 检查是否有关联的渲染工具 + * @return true如果有关联的渲染工具 + */ + public boolean hasAssociatedRanderTools() { + return associatedRanderTools != null; + } + + @Override + public String toString() { + return toolName; + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java new file mode 100644 index 0000000..7574ba1 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java @@ -0,0 +1,381 @@ +package com.chuangzhou.vivid2D.render.awt.tools; + +import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel; +import com.chuangzhou.vivid2D.render.model.Model2D; +import com.chuangzhou.vivid2D.render.model.ModelPart; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex; +import com.chuangzhou.vivid2D.render.model.util.BoundingBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.util.List; + +/** + * 顶点变形工具 + * 用于通过二级顶点对网格进行精细变形 + */ +public class VertexDeformationTool extends Tool { + private static final Logger logger = LoggerFactory.getLogger(VertexDeformationTool.class); + + private Mesh2D targetMesh = null; + private SecondaryVertex selectedVertex = null; + private SecondaryVertex hoveredVertex = null; + + private static final float VERTEX_TOLERANCE = 8.0f; + private ModelRenderPanel.DragMode currentDragMode = ModelRenderPanel.DragMode.NONE; + private float dragStartX, dragStartY; + + public VertexDeformationTool(ModelRenderPanel renderPanel) { + super(renderPanel, "顶点变形工具", "通过二级顶点对网格进行精细变形操作"); + } + + @Override + public void activate() { + if (isActive) return; + + isActive = true; + + // 尝试获取选中的网格,如果没有选中则使用第一个可见网格 + if (!renderPanel.getSelectedMeshes().isEmpty()) { + targetMesh = renderPanel.getSelectedMesh(); + } else { + // 如果没有选中的网格,尝试获取第一个可见网格 + targetMesh = findFirstVisibleMesh(); + } + + if (targetMesh != null) { + // 显示二级顶点 + associatedRanderTools.setAlgorithmEnabled("showSecondaryVertices", true); + targetMesh.setShowSecondaryVertices(true); + targetMesh.setRenderVertices(true); + + // 如果没有二级顶点,创建默认的四个角点 + if (targetMesh.getSecondaryVertexCount() == 0) { + createDefaultSecondaryVertices(); + } + + logger.info("激活顶点变形工具: {}", targetMesh.getName()); + } else { + logger.warn("没有找到可用的网格用于顶点变形"); + } + } + + @Override + public void deactivate() { + if (!isActive) return; + + isActive = false; + if (targetMesh != null) { + associatedRanderTools.setAlgorithmEnabled("showSecondaryVertices", false); + targetMesh.setShowSecondaryVertices(false); + targetMesh.setRenderVertices(false); + } + targetMesh = null; + selectedVertex = null; + hoveredVertex = null; + currentDragMode = ModelRenderPanel.DragMode.NONE; + + logger.info("停用顶点变形工具"); + } + + @Override + public void onMousePressed(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 选择二级顶点 + SecondaryVertex clickedVertex = findSecondaryVertexAtPosition(modelX, modelY); + if (clickedVertex != null) { + targetMesh.setSelectedSecondaryVertex(clickedVertex); + selectedVertex = clickedVertex; + + // 开始拖拽 + currentDragMode = ModelRenderPanel.DragMode.MOVE_SECONDARY_VERTEX; + dragStartX = modelX; + dragStartY = modelY; + + logger.debug("开始移动二级顶点: ID={}, 位置({}, {})", + clickedVertex.getId(), modelX, modelY); + } else { + // 点击空白处,取消选择 + targetMesh.setSelectedSecondaryVertex(null); + selectedVertex = null; + currentDragMode = ModelRenderPanel.DragMode.NONE; + } + } + + @Override + public void onMouseReleased(MouseEvent e, float modelX, float modelY) { + if (!isActive) return; + + // 记录操作历史 + if (currentDragMode == ModelRenderPanel.DragMode.MOVE_SECONDARY_VERTEX && selectedVertex != null) { + logger.debug("完成移动二级顶点: ID={}", selectedVertex.getId()); + } + + currentDragMode = ModelRenderPanel.DragMode.NONE; + } + + @Override + public void onMouseDragged(MouseEvent e, float modelX, float modelY) { + if (!isActive || selectedVertex == null) return; + + if (currentDragMode == ModelRenderPanel.DragMode.MOVE_SECONDARY_VERTEX) { + float deltaX = modelX - dragStartX; + float deltaY = modelY - dragStartY; + + // 移动顶点到新位置 + selectedVertex.setPosition(modelX, modelY); + + // 更新拖拽起始位置 + dragStartX = modelX; + dragStartY = modelY; + + // 标记网格为脏状态,需要重新计算边界等 + targetMesh.markDirty(); + targetMesh.updateBounds(); + + // 强制重绘 + renderPanel.repaint(); + } + } + + @Override + public void onMouseMoved(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 更新悬停的二级顶点 + SecondaryVertex newHoveredVertex = findSecondaryVertexAtPosition(modelX, modelY); + + if (newHoveredVertex != hoveredVertex) { + hoveredVertex = newHoveredVertex; + + // 更新光标 + if (hoveredVertex != null) { + renderPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } else { + renderPanel.setCursor(createVertexCursor()); + } + } + } + + @Override + public void onMouseClicked(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 如果点击了空白处且没有顶点被选中,可以创建新顶点 + if (selectedVertex == null && findSecondaryVertexAtPosition(modelX, modelY) == null) { + // 这里可以选择是否允许通过单击创建顶点 + // createSecondaryVertexAt(modelX, modelY); + } + } + + @Override + public void onMouseDoubleClicked(MouseEvent e, float modelX, float modelY) { + if (!isActive || targetMesh == null) return; + + // 检查是否双击了二级顶点 + SecondaryVertex clickedVertex = findSecondaryVertexAtPosition(modelX, modelY); + if (clickedVertex != null) { + // 双击二级顶点:删除该顶点 + deleteSecondaryVertex(clickedVertex); + } else { + // 双击空白处:创建新的二级顶点 + createSecondaryVertexAt(modelX, modelY); + } + } + + @Override + public Cursor getToolCursor() { + return createVertexCursor(); + } + + // ================== 工具特定方法 ================== + + /** + * 查找第一个可见的网格 + */ + private Mesh2D findFirstVisibleMesh() { + Model2D model = renderPanel.getModel(); + if (model == null) return null; + + List parts = model.getParts(); + if (parts == null || parts.isEmpty()) return null; + + for (ModelPart part : parts) { + if (part != null && part.isVisible()) { + List meshes = part.getMeshes(); + if (meshes != null && !meshes.isEmpty()) { + for (Mesh2D mesh : meshes) { + if (mesh != null && mesh.isVisible()) { + return mesh; + } + } + } + } + } + return null; + } + + /** + * 创建默认的四个角点二级顶点 + */ + private void createDefaultSecondaryVertices() { + if (targetMesh == null) return; + + // 确保边界框是最新的 + targetMesh.updateBounds(); + BoundingBox bounds = targetMesh.getBounds(); + if (bounds == null || !bounds.isValid()) { + logger.warn("无法为网格 {} 创建默认二级顶点:边界框无效", targetMesh.getName()); + return; + } + + float minX = bounds.getMinX(); + float minY = bounds.getMinY(); + float maxX = bounds.getMaxX(); + float maxY = bounds.getMaxY(); + + // 创建四个角点 + targetMesh.addSecondaryVertex(minX, minY, 0.0f, 1.0f); // 左下 + targetMesh.addSecondaryVertex(maxX, minY, 1.0f, 1.0f); // 右下 + targetMesh.addSecondaryVertex(maxX, maxY, 1.0f, 0.0f); // 右上 + targetMesh.addSecondaryVertex(minX, maxY, 0.0f, 0.0f); // 左上 + + logger.debug("为网格 {} 创建了4个默认二级顶点", targetMesh.getName()); + } + + /** + * 在指定位置创建二级顶点 + */ + private void createSecondaryVertexAt(float x, float y) { + if (targetMesh == null) return; + + // 确保边界框是最新的 + targetMesh.updateBounds(); + BoundingBox bounds = targetMesh.getBounds(); + if (bounds == null || !bounds.isValid()) { + logger.warn("无法创建二级顶点:边界框无效"); + return; + } + + // 计算UV坐标(基于边界框) + float u = (x - bounds.getMinX()) / bounds.getWidth(); + float v = (y - bounds.getMinY()) / bounds.getHeight(); + + // 限制UV在0-1范围内 + u = Math.max(0.0f, Math.min(1.0f, u)); + v = Math.max(0.0f, Math.min(1.0f, v)); + + SecondaryVertex newVertex = targetMesh.addSecondaryVertex(x, y, u, v); + if (newVertex != null) { + logger.info("创建二级顶点: ID={}, 位置({}, {}), UV({}, {})", + newVertex.getId(), x, y, u, v); + + // 标记网格为脏状态 + targetMesh.markDirty(); + renderPanel.repaint(); + } else { + logger.warn("创建二级顶点失败"); + } + } + + /** + * 删除二级顶点 + */ + private void deleteSecondaryVertex(SecondaryVertex vertex) { + if (targetMesh == null || vertex == null) return; + + boolean removed = targetMesh.removeSecondaryVertex(vertex); + if (removed) { + if (selectedVertex == vertex) { + selectedVertex = null; + } + if (hoveredVertex == vertex) { + hoveredVertex = null; + } + logger.info("删除二级顶点: ID={}", vertex.getId()); + + // 标记网格为脏状态 + targetMesh.markDirty(); + renderPanel.repaint(); + } else { + logger.warn("删除二级顶点失败: ID={}", vertex.getId()); + } + } + + /** + * 在指定位置查找二级顶点 + */ + private SecondaryVertex findSecondaryVertexAtPosition(float x, float y) { + if (targetMesh == null) return null; + + float tolerance = VERTEX_TOLERANCE / calculateScaleFactor(); + return targetMesh.selectSecondaryVertexAt(x, y, tolerance); + } + + /** + * 计算当前缩放因子 + */ + private float calculateScaleFactor() { + return renderPanel.getCameraManagement().calculateScaleFactor(); + } + + /** + * 创建顶点工具光标 + */ + private Cursor createVertexCursor() { + int size = 32; + BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = cursorImg.createGraphics(); + + // 设置抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 绘制透明背景 + g2d.setColor(new Color(0, 0, 0, 0)); + g2d.fillRect(0, 0, size, size); + + // 绘制顶点图标 + int center = size / 2; + + // 外圈 + g2d.setColor(Color.GREEN); + g2d.setStroke(new BasicStroke(2f)); + g2d.drawOval(center - 6, center - 6, 12, 12); + + // 内圈 + g2d.setColor(new Color(0, 200, 0, 150)); + g2d.setStroke(new BasicStroke(1f)); + g2d.drawOval(center - 3, center - 3, 6, 6); + + // 中心点 + g2d.setColor(Color.GREEN); + g2d.fillOval(center - 1, center - 1, 2, 2); + + g2d.dispose(); + + return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "VertexCursor"); + } + + // ================== 获取工具状态 ================== + + public Mesh2D getTargetMesh() { + return targetMesh; + } + + public SecondaryVertex getSelectedVertex() { + return selectedVertex; + } + + public SecondaryVertex getHoveredVertex() { + return hoveredVertex; + } + + public boolean isDragging() { + return currentDragMode == ModelRenderPanel.DragMode.MOVE_SECONDARY_VERTEX; + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/manager/RanderToolsManager.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/manager/RanderToolsManager.java new file mode 100644 index 0000000..a0e628f --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/manager/RanderToolsManager.java @@ -0,0 +1,128 @@ +package com.chuangzhou.vivid2D.render.model.util.manager; + +import com.chuangzhou.vivid2D.render.awt.tools.Tool; +import com.chuangzhou.vivid2D.render.model.util.tools.RanderTools; +import org.joml.Matrix3f; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 渲染工具管理器 + * 负责管理Tool和RanderTools之间的绑定关系 + */ +public class RanderToolsManager { + + // 存储Tool和RanderTools的绑定关系 + private final Map toolRanderToolsMap; + + // 单例实例 + private static volatile RanderToolsManager instance; + + /** + * 私有构造函数 + */ + private RanderToolsManager() { + this.toolRanderToolsMap = new ConcurrentHashMap<>(); + } + + /** + * 获取单例实例 + * @return RanderToolsManager单例 + */ + public static RanderToolsManager getInstance() { + if (instance == null) { + synchronized (RanderToolsManager.class) { + if (instance == null) { + instance = new RanderToolsManager(); + } + } + } + return instance; + } + + // ================== 核心方法 ================== + + /** + * 绑定Tool和RanderTools(自动初始化) + * @param tool Tool对象 + * @param randerTools RanderTools对象 + * @return 是否绑定成功 + */ + public boolean bindToolWithRanderTools(Tool tool, RanderTools randerTools) { + if (tool == null || randerTools == null) { + return false; + } + toolRanderToolsMap.put(tool, randerTools); + tool.setAssociatedRanderTools(randerTools); + randerTools.init(); + return true; + } + + + /** + * 一键渲染所有绑定的工具 + * @param modelMatrix 模型矩阵 + * @param renderContext 渲染上下文 + * @return 成功渲染的工具数量 + */ + public int renderAllTools(Matrix3f modelMatrix, Object renderContext) { + int successCount = 0; + for (RanderTools randerTools : toolRanderToolsMap.values()) { + try { + if (randerTools.render(modelMatrix, renderContext)) { + successCount++; + } + } catch (Exception e) { + System.err.println("渲染工具执行失败: " + randerTools.getClass().getSimpleName() + " - " + e.getMessage()); + } + } + return successCount; + } + + // ================== 查询方法 ================== + + /** + * 获取Tool对应的RanderTools + * @param tool Tool对象 + * @return 对应的RanderTools,如果没有绑定返回null + */ + public RanderTools getRanderToolsForTool(Tool tool) { + return toolRanderToolsMap.get(tool); + } + + /** + * 检查Tool是否已绑定RanderTools + * @param tool Tool对象 + * @return 是否已绑定 + */ + public boolean isToolBound(Tool tool) { + return toolRanderToolsMap.containsKey(tool); + } + + /** + * 获取绑定数量 + * @return 当前绑定数量 + */ + public int getBindingCount() { + return toolRanderToolsMap.size(); + } + + // ================== 清理方法 ================== + + /** + * 清理所有绑定 + */ + public void clearAllBindings() { + // 清理Tool中的关联引用 + for (Tool tool : toolRanderToolsMap.keySet()) { + tool.setAssociatedRanderTools(null); + } + toolRanderToolsMap.clear(); + } + + @Override + public String toString() { + return String.format("RanderToolsManager[bindings=%d]", getBindingCount()); + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java new file mode 100644 index 0000000..052e41b --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/LiquifyTargetPartRander.java @@ -0,0 +1,363 @@ +package com.chuangzhou.vivid2D.render.model.util.tools; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.TextRenderer; +import com.chuangzhou.vivid2D.render.model.util.BoundingBox; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.systems.RenderSystem; +import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder; +import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator; +import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement; +import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram; +import org.joml.Matrix3f; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL11; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * LiquifyTargetPartRander - 优化版(针对 isRenderVertices 大网格卡顿做了多项性能优化) + * + * 优化点: + * 1. 对大网格(顶点数超过阈值)降低绘制细节:不绘制内部三角线,采样绘制顶点(点与编号减少频率)。 + * 2. 将多次 begin()/endImmediate() 批次化:尽量合并连续的线段绘制到单次 begin/end。 + * 3. 降低每顶点的内存分配与 API 调用(把文本渲染请求收集、复用局部变量)。 + * 4. dashed line 绘制改为在小段数量内合并绘制或直接绘制单条轻量直线以减少 draw call。 + * 5. 对 circle/point 的 segment 数做自适应限制(越大网格越少细分)。 + */ +public class LiquifyTargetPartRander extends RanderTools { + private final Vector4f liquifyOverlayColor = new Vector4f(1.0f, 0.5f, 0.0f, 0.3f); // 半透明橙色 + + // 可调阈值(根据你的机器/场景调小或调大) + private static final int LARGE_VERTEX_THRESHOLD = 1500; // 超过则使用低质量绘制 + private static final int TARGET_VERTEX_DRAW = 300; // 大网格时目标绘制的顶点数量 + + @Override + public void init(Map algorithmEnabled) { + algorithmEnabled.put("showLiquifyOverlay", false); + algorithmEnabled.put("isRenderVertices", false); + } + + @Override + public boolean render(Matrix3f modelMatrix,Object renderContext) { + if (renderContext instanceof Mesh2D mesh2D) { + drawLiquifyOverlay(mesh2D, modelMatrix); + return true; + } + return false; + } + + /** + * 主渲染入口。收集文本命令到 pendingTexts,绘制完成后 popState,再把文本绘制出来(外部渲染) + */ + private void drawLiquifyOverlay(Mesh2D mesh2D, Matrix3f modelMatrix) { + if (!isAlgorithmEnabled("showLiquifyOverlay")) return; + + List pendingTexts = new ArrayList<>(); + + RenderSystem.pushState(); + try { + ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader"); + if (solidShader != null && solidShader.programId != 0) { + solidShader.use(); + int modelLoc = solidShader.getUniformLocation("uModelMatrix"); + if (modelLoc != -1) RenderSystem.uniformMatrix3(modelLoc, modelMatrix); + } + + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + Tesselator t = Tesselator.getInstance(); + BufferBuilder bb = t.getBuilder(); + + BoundingBox bounds = mesh2D.getBounds(); + if (bounds == null || !bounds.isValid()) return; + + if (!isAlgorithmEnabled("isRenderVertices")) { + // 普通模式:美观渲染(轻填充 + 多层轮廓) + Vector4f shadowCol = new Vector4f(0f, 0f, 0f, 0.08f); + drawRoundedBoundingFill(bb, bounds, shadowCol, 6); + + Vector4f innerFill = new Vector4f(liquifyOverlayColor.x, liquifyOverlayColor.y, liquifyOverlayColor.z, 0.06f); + drawRoundedBoundingFill(bb, bounds, innerFill, 4); + + float[] alphas = new float[]{0.42f, 0.24f, 0.12f}; + float[] expand = new float[]{6f, 3f, 0f}; + for (int i = 0; i < alphas.length; i++) { + Vector4f stroke = new Vector4f(liquifyOverlayColor.x, liquifyOverlayColor.y, liquifyOverlayColor.z, alphas[i]); + drawRoundedBoundingOutline(bb, bounds, stroke, Math.max(1f, 2.5f - i), expand[i]); + } + + collectLiquifyStatusText(mesh2D, pendingTexts); + + } else { + // isRenderVertices 模式:性能优先 + 少量视觉信息 + Vector4f outline = new Vector4f(liquifyOverlayColor.x, liquifyOverlayColor.y, liquifyOverlayColor.z, 0.16f); + drawRoundedBoundingOutline(bb, bounds, outline, 1.0f, 0f); + + // 优化点:根据顶点数量选择低/高质量绘制 + int vertexCount = mesh2D.getVertices() == null ? 0 : mesh2D.getVertices().length / 2; + boolean large = vertexCount > LARGE_VERTEX_THRESHOLD; + + // 边框:始终绘制(合并一次 begin/end) + drawOutlineOnce(bb, mesh2D); + + // 内部线:大网格时跳过以减轻负担 + if (!large && mesh2D.getIndices() != null && mesh2D.getIndices().length >= 3) { + // 合并内线绘制(批量) + drawIndexedInternalLinesBatch(bb, mesh2D); + } + + // 顶点点标记:采样绘制以降低数量 + drawLiquifyVertexPointsOptimized(mesh2D, bb, pendingTexts, large, vertexCount); + drawCompactLiquifyBadge(mesh2D, bb, bounds); + } + + } finally { + RenderSystem.popState(); + } + + // 延迟绘制文本(在外层安全) + if (!pendingTexts.isEmpty()) { + TextRenderer tr = ModelRender.getTextRenderer(); + if (tr != null) { + for (TextItem ti : pendingTexts) { + try { + ModelRender.renderText(ti.text, ti.x, ti.y, ti.color); + } catch (Exception ignored) {} + } + } + } + } + + // ========== 辅助方法 ========== + + private void drawRoundedBoundingFill(BufferBuilder bb, BoundingBox bounds, Vector4f color, float padding) { + float minX = bounds.getMinX() - padding; + float minY = bounds.getMinY() - padding; + float maxX = bounds.getMaxX() + padding; + float maxY = bounds.getMaxY() + padding; + + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(color); + bb.vertex(minX, minY, 0f, 0f); + bb.vertex(maxX, minY, 0f, 0f); + bb.vertex(maxX, maxY, 0f, 0f); + + bb.vertex(maxX, maxY, 0f, 0f); + bb.vertex(minX, maxY, 0f, 0f); + bb.vertex(minX, minY, 0f, 0f); + bb.endImmediate(); + } + + private void drawRoundedBoundingOutline(BufferBuilder bb, BoundingBox bounds, Vector4f color, float lineWidth, float padding) { + float minX = bounds.getMinX() - padding; + float minY = bounds.getMinY() - padding; + float maxX = bounds.getMaxX() + padding; + float maxY = bounds.getMaxY() + padding; + + float lw = Math.max(1.0f, Math.min(lineWidth, 4.0f)); // 限制线宽,避免驱动开销 + GL11.glLineWidth(lw); + bb.begin(GL11.GL_LINE_LOOP, 4); + bb.setColor(color); + bb.vertex(minX, minY, 0f, 0f); + bb.vertex(maxX, minY, 0f, 0f); + bb.vertex(maxX, maxY, 0f, 0f); + bb.vertex(minX, maxY, 0f, 0f); + bb.endImmediate(); + GL11.glLineWidth(1.0f); + } + + // 合并绘制外轮廓(单次 begin/end) + private void drawOutlineOnce(BufferBuilder bb, Mesh2D mesh2D) { + if (mesh2D.getVertices() == null || mesh2D.getVertices().length < 4) return; + Vector4f OUTER_LINE = new Vector4f(1f, 0.85f, 0.35f, 0.12f); + int count = mesh2D.getVertices().length / 2; + GL11.glLineWidth(1.0f); + bb.begin(GL11.GL_LINE_LOOP, count); + bb.setColor(OUTER_LINE); + float[] verts = mesh2D.getVertices(); + for (int i = 0; i < count; i++) { + int base = i * 2; + bb.vertex(verts[base], verts[base + 1], 0f, 0f); + } + bb.endImmediate(); + } + + // 批量绘制内部索引线(只在小网格下使用,避免大量 drawcalls) + private void drawIndexedInternalLinesBatch(BufferBuilder bb, Mesh2D mesh2D) { + int[] idx = mesh2D.getIndices(); + if (idx == null || idx.length < 3) return; + Vector4f innerLine = new Vector4f(1f, 0.6f, 0.0f, 0.06f); + // 每三角产生3条线 => 最多 idx.length lines. 合并为一次 GL_LINES 绘制 + int lines = (idx.length / 3) * 6; // 每三角 6 顶点(3 条线) + bb.begin(GL11.GL_LINES, lines); + bb.setColor(innerLine); + float[] verts = mesh2D.getVertices(); + for (int i = 0; i < idx.length; i += 3) { + int i1 = idx[i]; + int i2 = idx[i + 1]; + int i3 = idx[i + 2]; + // line i1-i2 + bb.vertex(verts[i1 * 2], verts[i1 * 2 + 1], 0f, 0f); + bb.vertex(verts[i2 * 2], verts[i2 * 2 + 1], 0f, 0f); + // line i2-i3 + bb.vertex(verts[i2 * 2], verts[i2 * 2 + 1], 0f, 0f); + bb.vertex(verts[i3 * 2], verts[i3 * 2 + 1], 0f, 0f); + // line i3-i1 + bb.vertex(verts[i3 * 2], verts[i3 * 2 + 1], 0f, 0f); + bb.vertex(verts[i1 * 2], verts[i1 * 2 + 1], 0f, 0f); + } + bb.endImmediate(); + } + + // 顶点点标记 - 优化版:大网格时采样绘制、减少 segment、合并操作 + private void drawLiquifyVertexPointsOptimized(Mesh2D mesh2D, BufferBuilder bb, List pendingTexts, boolean large, int vertexCount) { + if (mesh2D.getVertices() == 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(); + + // 采样步长,保证绘制点数量不会超过 TARGET_VERTEX_DRAW(越大网格步长越大) + int step = 1; + if (large && vertexCount > TARGET_VERTEX_DRAW) { + step = Math.max(1, vertexCount / TARGET_VERTEX_DRAW); + } + + // 批量绘制填充圆(尽量减少 begin/end) + // 我们使用较低分段数(segments)以降低每个圆的开销 + int segments = large ? 8 : 12; + + // 为减少 draw calls:先填充所有圆(多个 TRIANGLE_FAN 批次合并不现实,因为每个中心不同) + // 但我们至少减少每个圆的 segments 和避免不必要的计算 + for (int i = 0; i < vertexCount; i += step) { + int base = i * 2; + float x = verts[base]; + float y = verts[base + 1]; + + drawHollowCircleOptimized(bb, x, y, BASE_SIZE, FILL, STROKE, segments); + } + + // 编号仅在非常小网格或明确需要时绘制(避免大量文本) + boolean showIndex = false; + if (showIndex && !large) { + TextRenderer tr = ModelRender.getTextRenderer(); + if (tr != null) { + for (int i = 0; i < vertexCount; i += 1) { + int base = i * 2; + float x = verts[base]; + float y = verts[base + 1]; + String numberText = String.valueOf(i); + float textWidth = tr.getTextWidth(numberText); + float textX = x + 6.0f; + float textY = y - 4.0f; + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(new Vector4f(0.06f, 0.06f, 0.06f, 0.7f)); + bb.vertex(textX - 2, textY - 8, 0f, 0f); + bb.vertex(textX + textWidth + 2, textY - 8, 0f, 0f); + bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f); + bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f); + bb.vertex(textX - 2, textY + 2, 0f, 0f); + bb.vertex(textX - 2, textY - 8, 0f, 0f); + bb.endImmediate(); + pendingTexts.add(new TextItem(numberText, textX, textY, new Vector4f(1f,1f,1f,0.88f))); + } + } + } + } + + // 更轻量的空心圆绘制(segments 少) + private void drawHollowCircleOptimized(BufferBuilder bb, float cx, float cy, float radius, Vector4f fill, Vector4f stroke, int segments) { + if (radius <= 0f) return; + segments = Math.max(6, Math.min(segments, 16)); + // 填充(每个圆仍需一次 TRIANGLE_FAN) + bb.begin(GL11.GL_TRIANGLE_FAN, segments + 2); + bb.setColor(fill); + bb.vertex(cx, cy, 0f, 0f); + for (int i = 0; i <= segments; i++) { + double ang = 2.0 * Math.PI * i / segments; + float x = cx + (float) Math.cos(ang) * radius; + float y = cy + (float) Math.sin(ang) * radius; + bb.setColor(fill); + bb.vertex(x, y, 0f, 0f); + } + bb.endImmediate(); + + // 描边(LINE_LOOP) + bb.begin(GL11.GL_LINE_LOOP, segments); + bb.setColor(stroke); + for (int i = 0; i < segments; i++) { + double ang = 2.0 * Math.PI * i / segments; + float x = cx + (float) Math.cos(ang) * radius; + float y = cy + (float) Math.sin(ang) * radius; + bb.vertex(x, y, 0f, 0f); + } + bb.endImmediate(); + } + + // compact badge(小徽章) + private void drawCompactLiquifyBadge(Mesh2D mesh2D, BufferBuilder bb, BoundingBox bounds) { + float badgeX = bounds.getMinX() + (bounds.getWidth() * 0.06f); + float badgeY = bounds.getMaxY() - (bounds.getHeight() * 0.06f); + float r = Math.min(bounds.getWidth(), bounds.getHeight()) * 0.035f; + r = Math.max(4f, Math.min(r, 18f)); + + int seg = 12; + bb.begin(GL11.GL_TRIANGLE_FAN, seg + 1); + bb.setColor(new Vector4f(1f, 0.6f, 0.1f, 0.62f)); + bb.vertex(badgeX, badgeY, 0f, 0f); + for (int i = 0; i <= seg; i++) { + float ang = (float) (i * 2 * Math.PI / seg); + bb.vertex(badgeX + (float)Math.cos(ang) * r, badgeY + (float)Math.sin(ang) * r, 0f, 0f); + } + bb.endImmediate(); + + drawSimpleBrushIcon(bb, badgeX, badgeY, r * 0.9f); + } + + private void drawSimpleBrushIcon(BufferBuilder bb, float centerX, float centerY, float size) { + float iconSize = Math.max(1f, size * 0.6f); + bb.begin(GL11.GL_LINES, 2); + bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 0.9f)); + bb.vertex(centerX - iconSize * 0.3f, centerY - iconSize * 0.5f, 0f, 0f); + bb.vertex(centerX, centerY + iconSize * 0.3f, 0f, 0f); + bb.endImmediate(); + bb.begin(GL11.GL_TRIANGLES, 3); + bb.setColor(new Vector4f(1.0f, 0.3f, 0.0f, 0.9f)); + bb.vertex(centerX, centerY + iconSize * 0.3f, 0f, 0f); + bb.vertex(centerX - iconSize * 0.2f, centerY + iconSize * 0.6f, 0f, 0f); + bb.vertex(centerX + iconSize * 0.2f, centerY + iconSize * 0.6f, 0f, 0f); + bb.endImmediate(); + } + + // 收集文本(在 pushState 内只收集,不渲染) + private void collectLiquifyStatusText(Mesh2D mesh2D, List pendingTexts) { + BoundingBox bounds = mesh2D.getBounds(); + if (bounds == null || !bounds.isValid()) return; + String liquifyText = "Liquify"; + String hintText = "Ctrl: Show Vertices"; + TextRenderer tr = ModelRender.getTextRenderer(); + if (tr == null) return; + + float centerX = (bounds.getMinX() + bounds.getMaxX()) / 2.0f; + float textY = bounds.getMaxY() + 20.0f; + float textWidth = tr.getTextWidth("LIQUIFY MODE"); + float textX = centerX - textWidth / 2.0f; + pendingTexts.add(new TextItem("LIQUIFY MODE", textX, textY, new Vector4f(1.0f, 0.8f, 0.0f, 1.0f))); + + float indicatorX = bounds.getMaxX() + Math.max(bounds.getWidth(), bounds.getHeight()) * 0.08f; + float indicatorY = bounds.getMaxY() - Math.max(bounds.getWidth(), bounds.getHeight()) * 0.06f; + float tx = indicatorX - tr.getTextWidth(liquifyText) / 2f; + float ty = indicatorY - Math.max(bounds.getWidth(), bounds.getHeight()) * 0.05f - 6f; + pendingTexts.add(new TextItem(liquifyText, tx, ty, new Vector4f(1f, 0.84f, 0.36f, 0.88f))); + pendingTexts.add(new TextItem(hintText, tx, ty + 14f, new Vector4f(0.8f, 0.8f, 0.8f, 0.65f))); + } + + // 临时文本项 + private record TextItem(String text, float x, float y, Vector4f color) { } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/RanderTools.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/RanderTools.java new file mode 100644 index 0000000..73218c8 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/RanderTools.java @@ -0,0 +1,147 @@ +package com.chuangzhou.vivid2D.render.model.util.tools; + +import org.joml.Matrix3f; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 抽象渲染工具类 + * 提供基础的算法启用标识管理和渲染功能 + */ +public abstract class RanderTools { + private static final Logger logger = LoggerFactory.getLogger(RanderTools.class); + + /** + * 算法启用标识 + * true表示算法启用,false表示算法禁用 + */ + protected Map algorithmEnabled = new LinkedHashMap<>(); + + /** + * 默认构造函数 + */ + public RanderTools() {} + + public void init(){ + init(algorithmEnabled); + } + + /** + * 初始化算法启用标识 + * @param algorithmEnabled 算法启用标识 + */ + public abstract void init(Map algorithmEnabled); + + /** + * 抽象渲染方法 + * 子类必须实现具体地渲染逻辑 + * + * @param renderContext 渲染上下文对象 + * @return 渲染结果,通常为boolean表示渲染成功与否 + */ + public abstract boolean render(Matrix3f modelMatrix,Object renderContext); + + /** + * 获取算法启用标识 + * @param algorithmName 算法名称 + * @return 算法启用标识 + */ + public boolean isAlgorithmEnabled(String algorithmName) { + return algorithmEnabled.getOrDefault(algorithmName, false); + } + + /** + * 获取算法启用标识 + * @return 算法启用标识 + */ + public Map getAlgorithmEnabled() { + return new LinkedHashMap<>(algorithmEnabled); + } + + /** + * 设置算法启用标识 + * @param algorithmName 算法名称 + * @param enabled 启用标识 + */ + public void setAlgorithmEnabled(String algorithmName, boolean enabled) { + // 如果算法名称不存在,自动添加 + if (!algorithmEnabled.containsKey(algorithmName)) { + logger.warn("算法名称不存在,自动添加:{}", algorithmName); + } + algorithmEnabled.put(algorithmName, enabled); + } + + /** + * 添加新的算法标识 + * @param algorithmName 算法名称 + * @param enabled 初始启用状态 + */ + public void addAlgorithm(String algorithmName, boolean enabled) { + algorithmEnabled.put(algorithmName, enabled); + logger.debug("添加算法:{},初始状态:{}", algorithmName, enabled); + } + + /** + * 移除算法标识 + * @param algorithmName 算法名称 + */ + public void removeAlgorithm(String algorithmName) { + if (algorithmEnabled.remove(algorithmName) != null) { + logger.debug("移除算法:{}", algorithmName); + } + } + + /** + * 启用所有算法 + */ + public void enableAllAlgorithms() { + for (String algorithmName : algorithmEnabled.keySet()) { + algorithmEnabled.put(algorithmName, true); + } + } + + /** + * 禁用所有算法 + */ + public void disableAllAlgorithms() { + for (String algorithmName : algorithmEnabled.keySet()) { + algorithmEnabled.put(algorithmName, false); + } + } + + /** + * 检查算法是否存在 + * @param algorithmName 算法名称 + * @return 是否存在 + */ + public boolean hasAlgorithm(String algorithmName) { + return algorithmEnabled.containsKey(algorithmName); + } + + /** + * 获取工具状态信息 + * @return 包含工具状态信息的字符串 + */ + public String getStatus() { + return String.format("RanderTools[algorithmEnabled=%s]", algorithmEnabled); + } + + /** + * 获取详细状态信息 + * @return 详细状态字符串 + */ + public String getDetailedStatus() { + StringBuilder sb = new StringBuilder(); + sb.append("RanderTools Status:\n"); + sb.append(" Algorithms: ").append(algorithmEnabled.size()).append("\n"); + + for (Map.Entry entry : algorithmEnabled.entrySet()) { + sb.append(" - ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + + return sb.toString(); + } +} \ No newline at end of file