feat(render): 实现液化工具及键盘快捷键管理

- 完全重写ModelRenderPanel
- 添加液化工具类,支持网格液化变形操作- 实现顶点渲染优化,提升大网格绘制性能
- 添加键盘管理器,支持多种快捷键操作- 实现摄像机控制与缩放功能
- 添加工具切换与状态管理功能
- 支持液化模式下的顶点显示控制
- 实现撤销/重做等编辑操作快捷键
This commit is contained in:
tzdwindows 7
2025-10-26 18:22:12 +08:00
parent 71aa2b8699
commit 401263cd2b
15 changed files with 4441 additions and 2844 deletions

View File

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

View File

@@ -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<String, KeyStroke> customShortcuts = new HashMap<>();
private final Map<String, AbstractAction> 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<Tool> 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<String, String> getShortcutInfo() {
Map<String, String> info = new HashMap<>();
for (Map.Entry<String, KeyStroke> 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("键盘管理器已清理");
}
}

View File

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

View File

@@ -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<ModelPart> parts, Map<ModelPart, Vector2f> startPositions) {
if (operationHistory != null && parts != null && !parts.isEmpty()) {
List<Object> 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<ModelPart> parts, Map<ModelPart, Vector2f> startScales) {
if (operationHistory != null && parts != null && !parts.isEmpty()) {
List<Object> 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<ModelPart> parts, Map<ModelPart, Float> startRotations) {
if (operationHistory != null && parts != null && !parts.isEmpty()) {
List<Object> 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<ModelPart> parts, Map<ModelPart, Vector2f> startPivots) {
if (operationHistory != null && parts != null && !parts.isEmpty()) {
List<Object> 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("操作历史已清除");
}
}
}

View File

@@ -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<String, Tool> 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<Tool> 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() : "");
}
}

View File

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

View File

@@ -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<ModelPart> parts = model.getParts();
if (parts == null || parts.isEmpty()) return null;
for (ModelPart part : parts) {
if (part != null && part.isVisible()) {
java.util.List<Mesh2D> 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();
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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<ModelPart> parts = model.getParts();
if (parts == null || parts.isEmpty()) return null;
for (ModelPart part : parts) {
if (part != null && part.isVisible()) {
List<Mesh2D> 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;
}
}

View File

@@ -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<Tool, RanderTools> 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());
}
}

View File

@@ -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<String, Boolean> 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<TextItem> 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<TextItem> 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<TextItem> 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) { }
}

View File

@@ -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<String, Boolean> algorithmEnabled = new LinkedHashMap<>();
/**
* 默认构造函数
*/
public RanderTools() {}
public void init(){
init(algorithmEnabled);
}
/**
* 初始化算法启用标识
* @param algorithmEnabled 算法启用标识
*/
public abstract void init(Map<String, Boolean> 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<String, Boolean> 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<String, Boolean> entry : algorithmEnabled.entrySet()) {
sb.append(" - ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
return sb.toString();
}
}