refactor(render):重构网格变形引擎并优化顶点管理
- 重构 Mesh2D 的变形系统,采用基于三角剖分与重心坐标的映射方式 - 引入 BarycentricWeightInfo 类用于存储顶点的变形权重信息 - 新增 addControlPointAt 方法支持在指定坐标创建控制点- 实现 Delaunay 三角剖分算法用于构建控制点网格 -重写 applyDeformation 方法以提高变形计算效率 - 移除旧的基于多边形权重的变形逻辑- 优化顶点索引管理,将 indices 整合进 VertexList - 增强网格创建方法,生成更利于变形的钻石拓扑结构- 添加顶点删除与索引重映射功能- 修复 FrameInterpolator 中顶点标签设置逻辑错误 - 完善网格拷贝与比较方法,确保正确处理顶点数据- 更新 GPU 上传逻辑以适配新的顶点存储结构
This commit is contained in:
@@ -9,28 +9,32 @@ import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* MultiSelectionBoxRenderer — 修复摄像机缩放导致边框消失的问题(并保持美观)
|
||||
* MultiSelectionBoxRenderer — 美观版完整实现
|
||||
*
|
||||
* 要点:
|
||||
* - drawSelectBox/drawMultiSelectionBox 接收 cameraScale 参数(世界单位 -> 像素的比例)
|
||||
* - 将需要“屏幕稳定”的厚度与尺寸除以 cameraScale,保证缩放时视觉一致
|
||||
* - 兼容不传 cameraScale 的调用(默认 1.0f)
|
||||
* 视觉设计目标:
|
||||
* - 细腻的三层边框(外发光 -> 主边框 -> 内描边)
|
||||
* - 小巧但可见的手柄(角点与边中点略有区分)
|
||||
* - 精致的中心点(十字 + 细环)
|
||||
* - 虚线用于多选边框
|
||||
* - 批处理绘制以减少 Draw Call(尽量在一次 TRIANGLES 调用中绘制多数元素)
|
||||
*
|
||||
* 注:BufferBuilder 的 vertex(...) 方法签名与项目实现有关,示例中使用 (x,y,z,u) 占位。
|
||||
*/
|
||||
public class MultiSelectionBoxRenderer {
|
||||
|
||||
// -------------------- 配置常量(可调) --------------------
|
||||
// 尺寸(以世界坐标为基准,这些会根据 cameraScale 自动调整为屏幕稳定值)
|
||||
// 尺寸
|
||||
public static final float DEFAULT_CORNER_SIZE = 8.0f;
|
||||
public static final float DEFAULT_BORDER_THICKNESS = 2.5f;
|
||||
public static final float DEFAULT_DASH_LENGTH = 10.0f;
|
||||
public static final float DEFAULT_GAP_LENGTH = 6.0f;
|
||||
|
||||
// 视觉厚度分层(这些是“世界坐标基准值”,实际绘制时会 / cameraScale)
|
||||
private static final float OUTER_BORDER_THICKNESS = 0.8f;
|
||||
// 视觉厚度分层(更细腻)
|
||||
private static final float OUTER_BORDER_THICKNESS = 2.2f;
|
||||
private static final float MAIN_BORDER_THICKNESS = 0.6f;
|
||||
private static final float INNER_BORDER_THICKNESS = 0.2f;
|
||||
|
||||
// 手柄与中心点尺寸(世界坐标基准)
|
||||
// 手柄与中心点尺寸
|
||||
private static final float HANDLE_CORNER_SIZE = DEFAULT_CORNER_SIZE;
|
||||
private static final float HANDLE_MID_SIZE = 2.8f;
|
||||
private static final float CENTER_LINE_THICKNESS = 1.2f;
|
||||
@@ -46,113 +50,79 @@ public class MultiSelectionBoxRenderer {
|
||||
public static final Vector4f MULTI_SELECTION_HANDLE_COLOR = new Vector4f(1.0f, 0.9f, 0.0f, 1.0f); // 黄色手柄
|
||||
public static final Vector4f CENTER_POINT_COLOR = new Vector4f(1.0f, 0.2f, 0.2f, 1.0f); // 中心点红
|
||||
|
||||
// 小的保护常量
|
||||
private static final float MIN_CAMERA_SCALE = 1e-4f;
|
||||
private static final float MIN_THICKNESS_PIXELS = 0.5f; // 最小像素视觉厚度保护
|
||||
|
||||
// -------------------- 公共绘制 API(带 cameraScale 参数) --------------------
|
||||
// -------------------- 公共绘制 API --------------------
|
||||
|
||||
/**
|
||||
* 绘制单选的选择框(推荐:传入 cameraScale)
|
||||
* 绘制单选的选择框(主入口)
|
||||
*
|
||||
* @param bounds 包围盒(世界坐标)
|
||||
* @param pivot 旋转中心 / 中心点(世界坐标)
|
||||
* @param cameraScale 世界单位 -> 像素 比例(通常来自 CameraManagement.calculateScaleFactor())
|
||||
* @param bounds 包围盒(世界坐标)
|
||||
* @param pivot 旋转中心 / 中心点(世界坐标)
|
||||
*/
|
||||
public static void drawSelectBox(BoundingBox bounds, Vector2f pivot, float cameraScale) {
|
||||
public static void drawSelectBox(BoundingBox bounds, Vector2f pivot) {
|
||||
if (bounds == null || !bounds.isValid()) return;
|
||||
if (cameraScale <= 0f) cameraScale = MIN_CAMERA_SCALE;
|
||||
|
||||
float minX = bounds.getMinX();
|
||||
float minY = bounds.getMinY();
|
||||
float maxX = bounds.getMaxX();
|
||||
float maxY = bounds.getMaxY();
|
||||
|
||||
// 根据 cameraScale 计算“屏幕稳定”的厚度/尺寸
|
||||
float outerThickness = Math.max(MIN_THICKNESS_PIXELS / cameraScale, OUTER_BORDER_THICKNESS / cameraScale);
|
||||
float mainThickness = Math.max(MIN_THICKNESS_PIXELS / cameraScale, MAIN_BORDER_THICKNESS / cameraScale);
|
||||
float innerThickness = Math.max(MIN_THICKNESS_PIXELS / cameraScale, INNER_BORDER_THICKNESS / cameraScale);
|
||||
|
||||
float handleCornerSize = Math.max(2.0f / cameraScale, HANDLE_CORNER_SIZE / cameraScale);
|
||||
float handleMidSize = Math.max(2.0f / cameraScale, HANDLE_MID_SIZE / cameraScale);
|
||||
|
||||
float centerLineThickness = Math.max(1.0f / cameraScale, CENTER_LINE_THICKNESS / cameraScale);
|
||||
float centerRingThickness = Math.max(1.0f / cameraScale, CENTER_RING_THICKNESS / cameraScale);
|
||||
float centerRingRadius = Math.max(3.0f / cameraScale, CENTER_RING_RADIUS / cameraScale);
|
||||
|
||||
Tesselator tesselator = Tesselator.getInstance();
|
||||
BufferBuilder bb = tesselator.getBuilder();
|
||||
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// 估算顶点数并开始 TRIANGLES 绘制
|
||||
int estimatedQuads = (3 * 4) + 8 + 12;
|
||||
// 估算顶点数:三层边框 + 8 个手柄 + 中心点十字和圆环
|
||||
// 每个四边形使用 6 个顶点(两个三角形)
|
||||
int estimatedQuads = (3 * 4) + 8 + (6 * 4); // 近似估算(保守)
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, estimatedQuads * 6);
|
||||
|
||||
// 外层发光边框(较宽、透明)
|
||||
bb.setColor(SOLID_BORDER_COLOR_OUTER);
|
||||
addQuadLineLoop(bb, outerThickness, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
addQuadLineLoop(bb, OUTER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
|
||||
// 主边框(核心线)
|
||||
bb.setColor(SOLID_BORDER_COLOR_MAIN);
|
||||
addQuadLineLoop(bb, mainThickness, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
addQuadLineLoop(bb, MAIN_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
|
||||
// 内描边(细)
|
||||
bb.setColor(SOLID_BORDER_COLOR_INNER);
|
||||
addQuadLineLoop(bb, innerThickness, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
addQuadLineLoop(bb, INNER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
|
||||
// 手柄(角点与边中点)
|
||||
bb.setColor(HANDLE_COLOR);
|
||||
addHandleQuad(bb, minX, minY, handleCornerSize);
|
||||
addHandleQuad(bb, maxX, minY, handleCornerSize);
|
||||
addHandleQuad(bb, minX, maxY, handleCornerSize);
|
||||
addHandleQuad(bb, maxX, maxY, handleCornerSize);
|
||||
addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE);
|
||||
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, handleMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, handleMidSize);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, handleMidSize);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, handleMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
|
||||
|
||||
// 中心点:十字 + 环(圆环使用三角片段)
|
||||
bb.setColor(CENTER_POINT_COLOR);
|
||||
addQuadLine(bb, pivot.x - 6.0f / cameraScale, pivot.y, pivot.x + 6.0f / cameraScale, pivot.y, centerLineThickness);
|
||||
addQuadLine(bb, pivot.x, pivot.y - 6.0f / cameraScale, pivot.x, pivot.y + 6.0f / cameraScale, centerLineThickness);
|
||||
addRing(bb, pivot.x, pivot.y, centerRingRadius, centerRingThickness, 18);
|
||||
addQuadLine(bb, pivot.x - 6.0f, pivot.y, pivot.x + 6.0f, pivot.y, CENTER_LINE_THICKNESS);
|
||||
addQuadLine(bb, pivot.x, pivot.y - 6.0f, pivot.x, pivot.y + 6.0f, CENTER_LINE_THICKNESS);
|
||||
addRing(bb, pivot.x, pivot.y, CENTER_RING_RADIUS, CENTER_RING_THICKNESS, 18);
|
||||
|
||||
tesselator.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向后兼容:不传 cameraScale 时使用默认 1.0(即不做屏幕稳定)
|
||||
*/
|
||||
public static void drawSelectBox(BoundingBox bounds, Vector2f pivot) {
|
||||
drawSelectBox(bounds, pivot, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制多选框(推荐传入 cameraScale)
|
||||
* 绘制多选框(虚线 + 手柄)
|
||||
*
|
||||
* @param multiBounds 包围盒
|
||||
* @param cameraScale 世界单位 -> 像素 比例
|
||||
* @param multiBounds 多选包围盒
|
||||
*/
|
||||
public static void drawMultiSelectionBox(BoundingBox multiBounds, float cameraScale) {
|
||||
public static void drawMultiSelectionBox(BoundingBox multiBounds) {
|
||||
if (multiBounds == null || !multiBounds.isValid()) return;
|
||||
if (cameraScale <= 0f) cameraScale = MIN_CAMERA_SCALE;
|
||||
|
||||
float minX = multiBounds.getMinX();
|
||||
float minY = multiBounds.getMinY();
|
||||
float maxX = multiBounds.getMaxX();
|
||||
float maxY = multiBounds.getMaxY();
|
||||
|
||||
// 屏幕稳定尺寸
|
||||
float handleCornerSize = Math.max(2.0f / cameraScale, HANDLE_CORNER_SIZE / cameraScale);
|
||||
float handleMidSize = Math.max(2.0f / cameraScale, HANDLE_MID_SIZE / cameraScale);
|
||||
float centerLineThickness = Math.max(1.0f / cameraScale, CENTER_LINE_THICKNESS / cameraScale);
|
||||
|
||||
float dashLen = Math.max(3.0f / cameraScale, DEFAULT_DASH_LENGTH / cameraScale);
|
||||
float gapLen = Math.max(2.0f / cameraScale, DEFAULT_GAP_LENGTH / cameraScale);
|
||||
|
||||
Tesselator tesselator = Tesselator.getInstance();
|
||||
BufferBuilder bb = tesselator.getBuilder();
|
||||
|
||||
@@ -160,41 +130,37 @@ public class MultiSelectionBoxRenderer {
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// 1) 虚线边框(使用 GL_LINES)
|
||||
int estimatedSegments = Math.max(4, (int) Math.ceil((2f * (multiBounds.getWidth() + multiBounds.getHeight())) / (dashLen + gapLen)));
|
||||
int estimatedSegments = Math.max(4, (int) Math.ceil((2f * (multiBounds.getWidth() + multiBounds.getHeight())) / (DEFAULT_DASH_LENGTH + DEFAULT_GAP_LENGTH)));
|
||||
bb.begin(GL11.GL_LINES, estimatedSegments * 2);
|
||||
bb.setColor(DASHED_BORDER_COLOR);
|
||||
addDashedLineVertices(bb, minX, minY, maxX, minY, dashLen, gapLen);
|
||||
addDashedLineVertices(bb, maxX, minY, maxX, maxY, dashLen, gapLen);
|
||||
addDashedLineVertices(bb, maxX, maxY, minX, maxY, dashLen, gapLen);
|
||||
addDashedLineVertices(bb, minX, maxY, minX, minY, dashLen, gapLen);
|
||||
addDashedLineVertices(bb, minX, minY, maxX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
addDashedLineVertices(bb, maxX, minY, maxX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
addDashedLineVertices(bb, maxX, maxY, minX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
addDashedLineVertices(bb, minX, maxY, minX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
tesselator.end();
|
||||
|
||||
// 2) 手柄与中心(合并为一次三角形绘制)
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, (8 + 2) * 6);
|
||||
bb.setColor(MULTI_SELECTION_HANDLE_COLOR);
|
||||
addHandleQuad(bb, minX, minY, handleCornerSize);
|
||||
addHandleQuad(bb, maxX, minY, handleCornerSize);
|
||||
addHandleQuad(bb, minX, maxY, handleCornerSize);
|
||||
addHandleQuad(bb, maxX, maxY, handleCornerSize);
|
||||
addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE);
|
||||
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, handleMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, handleMidSize);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, handleMidSize);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, handleMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
|
||||
|
||||
Vector2f center = multiBounds.getCenter();
|
||||
bb.setColor(CENTER_POINT_COLOR);
|
||||
addQuadLine(bb, center.x - 6.0f / cameraScale, center.y, center.x + 6.0f / cameraScale, center.y, centerLineThickness);
|
||||
addQuadLine(bb, center.x, center.y - 6.0f / cameraScale, center.x, center.y + 6.0f / cameraScale, centerLineThickness);
|
||||
addQuadLine(bb, center.x - 6.0f, center.y, center.x + 6.0f, center.y, CENTER_LINE_THICKNESS);
|
||||
addQuadLine(bb, center.x, center.y - 6.0f, center.x, center.y + 6.0f, CENTER_LINE_THICKNESS);
|
||||
|
||||
tesselator.end();
|
||||
}
|
||||
|
||||
public static void drawMultiSelectionBox(BoundingBox multiBounds) {
|
||||
drawMultiSelectionBox(multiBounds, 1.0f);
|
||||
}
|
||||
|
||||
// -------------------- 辅助绘图方法(保持不变) --------------------
|
||||
// -------------------- 辅助绘图方法 --------------------
|
||||
|
||||
/**
|
||||
* 添加一个填充四边形(用两个三角形表示)
|
||||
@@ -219,7 +185,7 @@ public class MultiSelectionBoxRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一条由四边形模拟的线段(厚度以输入值为准)
|
||||
* 绘制一条由四边形模拟的线段(厚度可控)
|
||||
*/
|
||||
private static void addQuadLine(BufferBuilder bb, float x0, float y0, float x1, float y1, float thickness) {
|
||||
float dx = x1 - x0;
|
||||
@@ -249,7 +215,7 @@ public class MultiSelectionBoxRenderer {
|
||||
/**
|
||||
* 绘制一个闭合的四边形线环(用于边框三层绘制)
|
||||
*
|
||||
* @param thickness 厚度(以外部传入的值为准,通常已根据 cameraScale 调整)
|
||||
* @param thickness 厚度(世界坐标)
|
||||
* @param vertices 顶点序列 x1,y1,x2,y2,...
|
||||
*/
|
||||
private static void addQuadLineLoop(BufferBuilder bb, float thickness, float... vertices) {
|
||||
@@ -269,8 +235,8 @@ public class MultiSelectionBoxRenderer {
|
||||
*
|
||||
* @param cx 中心 x
|
||||
* @param cy 中心 y
|
||||
* @param radius 半径(通常已按 cameraScale 调整)
|
||||
* @param thickness 环厚度(通常已按 cameraScale 调整)
|
||||
* @param radius 半径
|
||||
* @param thickness 环厚度
|
||||
* @param segments 分段数(建议 >= 8)
|
||||
*/
|
||||
private static void addRing(BufferBuilder bb, float cx, float cy, float radius, float thickness, int segments) {
|
||||
@@ -305,8 +271,8 @@ public class MultiSelectionBoxRenderer {
|
||||
/**
|
||||
* 在两点之间生成虚线段顶点(使用 GL_LINES)
|
||||
*
|
||||
* @param dashLen 虚线长度(通常已按 cameraScale 调整)
|
||||
* @param gapLen 间隙长度(通常已按 cameraScale 调整)
|
||||
* @param dashLen 虚线长度(世界坐标)
|
||||
* @param gapLen 间隙长度(世界坐标)
|
||||
*/
|
||||
private static void addDashedLineVertices(BufferBuilder bb, float startX, float startY, float endX, float endY,
|
||||
float dashLen, float gapLen) {
|
||||
|
||||
@@ -458,35 +458,6 @@ public class ModelLayerPanel extends JPanel {
|
||||
setSelectedLayers(selectedParts);
|
||||
}
|
||||
|
||||
// 原始的单选拖拽逻辑 (为兼容老版本保留,但现在应主要使用 performBlockReorder)
|
||||
public void performVisualReorder(int visualFrom, int visualTo) {
|
||||
if (model == null) return;
|
||||
try {
|
||||
int size = listModel.getSize();
|
||||
if (visualFrom < 0 || visualFrom >= size) return;
|
||||
if (visualTo < 0) visualTo = 0;
|
||||
if (visualTo > size - 1) visualTo = size - 1;
|
||||
|
||||
ModelPart moved = listModel.get(visualFrom);
|
||||
if (!isDragging) {
|
||||
isDragging = true;
|
||||
draggedPart = moved;
|
||||
dragStartPosition = new Vector2f(moved.getPosition());
|
||||
}
|
||||
|
||||
List<ModelPart> visual = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) visual.add(listModel.get(i));
|
||||
moved = visual.remove(visualFrom);
|
||||
visual.add(visualTo, moved);
|
||||
|
||||
// 使用新的辅助方法更新 UI 和模型
|
||||
updateModelAndUIFromVisualList(visual, List.of(moved));
|
||||
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增方法】执行多选拖拽后的图层块重排序操作。
|
||||
* 供 CompositeLayerTransferHandler (原 LayerReorderTransferHandler) 调用。
|
||||
|
||||
@@ -16,25 +16,21 @@ import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VertexDeformationTool extends Tool {
|
||||
private static final Logger logger = LoggerFactory.getLogger(VertexDeformationTool.class);
|
||||
private Mesh2D targetMesh = null;
|
||||
|
||||
// [MODIFIED] 单选被替换为多选列表
|
||||
private final List<Vertex> selectedVertices = new ArrayList<>();
|
||||
|
||||
private Vertex hoveredVertex = null;
|
||||
private static final float VERTEX_TOLERANCE = 8.0f;
|
||||
private ModelRenderPanel.DragMode currentDragMode = ModelRenderPanel.DragMode.NONE;
|
||||
private float savedCameraRotation = Float.NaN;
|
||||
private Vector2f savedCameraScale = new Vector2f(1, 1);
|
||||
private boolean cameraStateSaved = false;
|
||||
|
||||
// [MODIFIED] 恢复使用 orderedControlVertices 来维护控制点的顺序
|
||||
private final List<Vertex> orderedControlVertices = new ArrayList<>();
|
||||
|
||||
// --- [新增] 用于“推/拉”模式的状态变量 ---
|
||||
private boolean isPushPullMode = false;
|
||||
private Vector2f dragStartPoint = null;
|
||||
private List<Vertex> dragBaseState = null; // 存储拖动开始时的顶点快照
|
||||
|
||||
public VertexDeformationTool(ModelRenderPanel renderPanel) {
|
||||
super(renderPanel, "顶点变形工具", "直接对网格顶点进行精细变形操作");
|
||||
}
|
||||
@@ -43,76 +39,37 @@ public class VertexDeformationTool extends Tool {
|
||||
public void activate() {
|
||||
if (isActive) return;
|
||||
isActive = true;
|
||||
|
||||
orderedControlVertices.clear();
|
||||
selectedVertices.clear();
|
||||
|
||||
hoveredVertex = null;
|
||||
if (!renderPanel.getSelectedMeshes().isEmpty()) {
|
||||
targetMesh = renderPanel.getSelectedMesh();
|
||||
} else {
|
||||
targetMesh = findFirstVisibleMesh();
|
||||
}
|
||||
|
||||
// 关键:在激活时重置部件变换,确保鼠标坐标与顶点局部坐标一致,解决偏移问题
|
||||
try {
|
||||
if (renderPanel.getCameraManagement() != null && targetMesh != null && targetMesh.getModelPart() != null) {
|
||||
savedCameraRotation = targetMesh.getModelPart().getRotation();
|
||||
savedCameraScale = new Vector2f(targetMesh.getModelPart().getScale());
|
||||
cameraStateSaved = true;
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
targetMesh.getModelPart().setRotation(0f);
|
||||
targetMesh.getModelPart().setScale(1f);
|
||||
targetMesh.getModelPart().updateMeshVertices();
|
||||
} catch (Throwable t) { logger.debug("设置部件默认状态时失败: {}", t.getMessage()); }
|
||||
});
|
||||
}
|
||||
} catch (Throwable t) { logger.debug("无法备份/设置相机状态: {}", t.getMessage()); }
|
||||
|
||||
if (targetMesh != null) {
|
||||
orderedControlVertices.clear();
|
||||
orderedControlVertices.addAll(targetMesh.getDeformationControlVertices());
|
||||
targetMesh.setStates("showDeformationVertices", true);
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
targetMesh.setRenderVertices(true);
|
||||
targetMesh.updateBounds();
|
||||
} catch (Throwable t) { logger.debug("激活顶点显示失败: {}", t.getMessage()); }
|
||||
});
|
||||
logger.info("激活顶点变形工具: {}", targetMesh.getName());
|
||||
logger.info("激活顶点变形工具: {},已加载 {} 个控制点。", targetMesh.getName(), orderedControlVertices.size());
|
||||
} else {
|
||||
logger.warn("没有找到可用的网格用于顶点变形");
|
||||
}
|
||||
renderPanel.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
if (!isActive) return;
|
||||
isActive = false;
|
||||
|
||||
try {
|
||||
if (cameraStateSaved && renderPanel.getCameraManagement() != null && targetMesh != null && targetMesh.getModelPart() != null) {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
targetMesh.getModelPart().setRotation(savedCameraRotation);
|
||||
targetMesh.getModelPart().setScale(savedCameraScale);
|
||||
targetMesh.getModelPart().updateMeshVertices();
|
||||
targetMesh.saveAsOriginal();
|
||||
} catch (Throwable t) { logger.debug("恢复部件状态失败: {}", t.getMessage()); }
|
||||
});
|
||||
}
|
||||
} catch (Throwable t) { logger.debug("无法恢复相机状态: {}", t.getMessage()); } finally {
|
||||
cameraStateSaved = false;
|
||||
savedCameraRotation = Float.NaN;
|
||||
savedCameraScale = new Vector2f(1, 1);
|
||||
}
|
||||
|
||||
if (targetMesh != null) {
|
||||
// 在停用前,清除所有顶点的DEFORMATION标签
|
||||
for (Vertex v : orderedControlVertices) {
|
||||
v.setTag(VertexTag.DEFAULT);
|
||||
v.setTag(VertexTag.DEFORMATION);
|
||||
}
|
||||
targetMesh.setDeformationControlVertices(new ArrayList<>());
|
||||
orderedControlVertices.clear();
|
||||
|
||||
targetMesh.setStates("showDeformationVertices", false);
|
||||
try {
|
||||
targetMesh.setRenderVertices(false);
|
||||
@@ -125,90 +82,145 @@ public class VertexDeformationTool extends Tool {
|
||||
}
|
||||
targetMesh = null;
|
||||
selectedVertices.clear();
|
||||
orderedControlVertices.clear();
|
||||
hoveredVertex = null;
|
||||
currentDragMode = ModelRenderPanel.DragMode.NONE;
|
||||
logger.info("停用顶点变形工具");
|
||||
}
|
||||
|
||||
/**
|
||||
* [MODIFIED] onMousePressed 现在支持 Ctrl 多选。
|
||||
* [已修正] onMousePressed 现在会检查 Alt 键来决定进入“推/拉”模式还是“控制点选择”模式。
|
||||
*/
|
||||
@Override
|
||||
public void onMousePressed(MouseEvent e, float modelX, float modelY) {
|
||||
if (!isActive || targetMesh == null) return;
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
Vertex clickedVertex = findDeformationVertexAtPosition(modelX, modelY);
|
||||
if (clickedVertex != null) {
|
||||
if (e.isControlDown()) {
|
||||
// Ctrl 多选逻辑
|
||||
if (selectedVertices.contains(clickedVertex)) {
|
||||
selectedVertices.remove(clickedVertex); // 已选中的,再次点击则取消
|
||||
} else {
|
||||
selectedVertices.add(clickedVertex); // 未选中的,添加到列表
|
||||
}
|
||||
} else {
|
||||
// 普通单击逻辑
|
||||
if (!selectedVertices.contains(clickedVertex)) {
|
||||
selectedVertices.clear();
|
||||
selectedVertices.add(clickedVertex);
|
||||
}
|
||||
}
|
||||
currentDragMode = ModelRenderPanel.DragMode.MOVE_PRIMARY_VERTEX;
|
||||
} else {
|
||||
// 点击空白处,取消所有选择
|
||||
if (!e.isControlDown()) {
|
||||
selectedVertices.clear();
|
||||
}
|
||||
currentDragMode = ModelRenderPanel.DragMode.NONE;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("onMousePressed (VertexDeformationTool) 处理失败", t);
|
||||
} finally {
|
||||
renderPanel.repaint();
|
||||
|
||||
// [核心修正] 检查 Alt 键是否被按下
|
||||
if (e.isAltDown()) {
|
||||
// --- 进入“推/拉”模式 ---
|
||||
isPushPullMode = true;
|
||||
dragStartPoint = new Vector2f(modelX, modelY);
|
||||
|
||||
// 创建当前网格顶点状态的快照,这是计算位移的基准
|
||||
dragBaseState = new ArrayList<>(targetMesh.getActiveVertexList().size());
|
||||
for(Vertex v : targetMesh.getActiveVertexList()){
|
||||
dragBaseState.add(v.copy()); // 必须是深拷贝
|
||||
}
|
||||
});
|
||||
|
||||
currentDragMode = ModelRenderPanel.DragMode.NONE; // 确保不触发控制点拖动
|
||||
logger.debug("进入推/拉模式,起点: ({}, {})", modelX, modelY);
|
||||
|
||||
} else {
|
||||
// --- 默认的“控制点选择”模式 ---
|
||||
isPushPullMode = false;
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
Vertex clickedVertex = findDeformationVertexAtPosition(modelX, modelY);
|
||||
if (clickedVertex != null) {
|
||||
if (e.isControlDown()) {
|
||||
if (selectedVertices.contains(clickedVertex)) {
|
||||
selectedVertices.remove(clickedVertex);
|
||||
} else {
|
||||
selectedVertices.add(clickedVertex);
|
||||
}
|
||||
} else {
|
||||
if (!selectedVertices.contains(clickedVertex)) {
|
||||
selectedVertices.clear();
|
||||
selectedVertices.add(clickedVertex);
|
||||
}
|
||||
}
|
||||
currentDragMode = ModelRenderPanel.DragMode.MOVE_PRIMARY_VERTEX;
|
||||
} else {
|
||||
if (!e.isControlDown()) {
|
||||
selectedVertices.clear();
|
||||
}
|
||||
currentDragMode = ModelRenderPanel.DragMode.NONE;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("onMousePressed (控制点模式) 处理失败", t);
|
||||
} finally {
|
||||
renderPanel.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [已修正] onMouseDragged 现在会根据模式执行不同的拖动逻辑。
|
||||
*/
|
||||
@Override
|
||||
public void onMouseDragged(MouseEvent e, float modelX, float modelY) {
|
||||
if (!isActive || targetMesh == null) return;
|
||||
|
||||
if (isPushPullMode) {
|
||||
// --- “推/拉”模式的逻辑 ---
|
||||
if (dragStartPoint == null || dragBaseState == null) return;
|
||||
|
||||
// 计算从按下鼠标开始的总位移
|
||||
Vector2f delta = new Vector2f(modelX, modelY).sub(dragStartPoint);
|
||||
|
||||
// 定义一个画笔半径 (可以设为可配置的)
|
||||
float radius = 50.0f;
|
||||
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
// 调用 Mesh2D 中已经存在的局部变形方法!
|
||||
targetMesh.applyLocalizedPush(dragBaseState, dragStartPoint, delta, radius);
|
||||
} catch (Throwable t) {
|
||||
logger.error("onMouseDragged (推/拉模式) 处理失败", t);
|
||||
} finally {
|
||||
renderPanel.repaint();
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// --- 默认的“控制点拖动”模式的逻辑 ---
|
||||
if (selectedVertices.isEmpty() || currentDragMode != ModelRenderPanel.DragMode.MOVE_PRIMARY_VERTEX) return;
|
||||
|
||||
Vertex primaryVertex = selectedVertices.get(selectedVertices.size() - 1);
|
||||
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
primaryVertex.position.set(modelX, modelY);
|
||||
} catch (Throwable t) {
|
||||
logger.error("onMouseDragged (控制点模式) 处理失败", t);
|
||||
} finally {
|
||||
renderPanel.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMouseReleased(MouseEvent e, float modelX, float modelY) {
|
||||
if (!isActive) return;
|
||||
if (currentDragMode == ModelRenderPanel.DragMode.MOVE_PRIMARY_VERTEX && !selectedVertices.isEmpty()) {
|
||||
|
||||
if (isPushPullMode) {
|
||||
// --- 清理“推/拉”模式的状态 ---
|
||||
isPushPullMode = false;
|
||||
dragStartPoint = null;
|
||||
dragBaseState = null;
|
||||
logger.debug("退出推/拉模式");
|
||||
}
|
||||
|
||||
// 无论是哪种模式,都在松开鼠标时固化变形
|
||||
if (targetMesh != null) {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
if (targetMesh != null) {
|
||||
targetMesh.saveAsOriginal();
|
||||
if (targetMesh.getModelPart() != null) {
|
||||
targetMesh.getModelPart().updateMeshVertices();
|
||||
}
|
||||
targetMesh.saveAsOriginal();
|
||||
if (targetMesh.getModelPart() != null) {
|
||||
targetMesh.getModelPart().updateMeshVertices();
|
||||
}
|
||||
} catch (Throwable t) { logger.error("onMouseReleased 保存基准失败", t); }
|
||||
});
|
||||
}
|
||||
|
||||
currentDragMode = ModelRenderPanel.DragMode.NONE;
|
||||
renderPanel.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMouseDragged(MouseEvent e, float modelX, float modelY) {
|
||||
if (!isActive || selectedVertices.isEmpty() || targetMesh == null || currentDragMode != ModelRenderPanel.DragMode.MOVE_PRIMARY_VERTEX) return;
|
||||
|
||||
// 我们需要计算位移,以便批量移动
|
||||
// (这里简化处理,只移动最后一个选中的点,完整的批量拖拽需要更复杂的逻辑)
|
||||
Vertex primaryVertex = selectedVertices.get(selectedVertices.size() - 1);
|
||||
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
primaryVertex.position.set(modelX, modelY);
|
||||
targetMesh.applyDeformation();
|
||||
} catch (Throwable t) {
|
||||
logger.error("onMouseDragged (VertexDeformationTool) 处理失败", t);
|
||||
} finally {
|
||||
renderPanel.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// onMouseMoved, onMouseClicked, onMouseDoubleClicked, onKeyPressed, 等方法保持不变...
|
||||
// ... (此处省略所有其他未修改的方法,请保留您文件中的原样)
|
||||
@Override
|
||||
public void onMouseMoved(MouseEvent e, float modelX, float modelY) {
|
||||
if (!isActive || targetMesh == null) return;
|
||||
@@ -220,25 +232,24 @@ public class VertexDeformationTool extends Tool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMouseClicked(MouseEvent e, float modelX, float modelY) {
|
||||
// No action
|
||||
}
|
||||
public void onMouseClicked(MouseEvent e, float modelX, float modelY) { }
|
||||
|
||||
/**
|
||||
* [MODIFIED] 双击逻辑现在是:双击已有控制点则删除,双击空白则添加。
|
||||
*/
|
||||
@Override
|
||||
public void onMouseDoubleClicked(MouseEvent e, float modelX, float modelY) {
|
||||
if (!isActive || targetMesh == null) return;
|
||||
if (!isActive || targetMesh == null || e.isAltDown()) return; // 在推拉模式下禁用双击
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
Vertex clickedVertex = findDeformationVertexAtPosition(modelX, modelY);
|
||||
if (clickedVertex != null) {
|
||||
// 双击已有的点 -> 删除
|
||||
untagDeformationVertex(clickedVertex);
|
||||
} else {
|
||||
// 双击空白处 -> 添加
|
||||
tagNearestVertexAsDeformation(modelX, modelY);
|
||||
Vertex newVertex = targetMesh.addControlPointAt(modelX, modelY);
|
||||
if (newVertex != null) {
|
||||
orderedControlVertices.add(newVertex);
|
||||
updateDeformationRegion();
|
||||
} else {
|
||||
logger.warn("在 ({}, {}) 添加控制点失败,可能点击位置在网格外部。", modelX, modelY);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("onMouseDoubleClicked 处理失败", t);
|
||||
@@ -246,16 +257,12 @@ public class VertexDeformationTool extends Tool {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [MODIFIED] 按键逻辑现在支持批量删除。
|
||||
*/
|
||||
@Override
|
||||
public void onKeyPressed(KeyEvent e) {
|
||||
if (!isActive || selectedVertices.isEmpty()) return;
|
||||
int kc = e.getKeyCode();
|
||||
if (kc == KeyEvent.VK_BACK_SPACE || kc == KeyEvent.VK_DELETE) {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
// 创建一个副本进行遍历,以避免在遍历时修改列表
|
||||
List<Vertex> toDelete = new ArrayList<>(selectedVertices);
|
||||
for (Vertex v : toDelete) {
|
||||
untagDeformationVertex(v);
|
||||
@@ -270,39 +277,15 @@ public class VertexDeformationTool extends Tool {
|
||||
return createVertexCursor();
|
||||
}
|
||||
|
||||
private void tagNearestVertexAsDeformation(float x, float y) {
|
||||
if (targetMesh == null) return;
|
||||
Vertex nearestVertex = null;
|
||||
float minDistanceSq = Float.MAX_VALUE;
|
||||
|
||||
for (Vertex v : targetMesh.getActiveVertexList()) {
|
||||
// 使用原始位置进行查找,因为它是稳定的
|
||||
if (v.getTag() != VertexTag.DEFORMATION) {
|
||||
float distSq = v.originalPosition.distanceSquared(x, y);
|
||||
if (distSq < minDistanceSq) {
|
||||
minDistanceSq = distSq;
|
||||
nearestVertex = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestVertex != null) {
|
||||
nearestVertex.setTag(VertexTag.DEFORMATION);
|
||||
if (!orderedControlVertices.contains(nearestVertex)) {
|
||||
orderedControlVertices.add(nearestVertex);
|
||||
}
|
||||
logger.info("已添加控制点 {} (总数: {})", targetMesh.getActiveVertexList().vertices.indexOf(nearestVertex), orderedControlVertices.size());
|
||||
updateDeformationRegion();
|
||||
}
|
||||
}
|
||||
|
||||
private void untagDeformationVertex(Vertex vertex) {
|
||||
if (targetMesh == null || vertex == null) return;
|
||||
vertex.setTag(VertexTag.DEFAULT);
|
||||
orderedControlVertices.remove(vertex);
|
||||
selectedVertices.remove(vertex); // 确保从选择列表中也移除
|
||||
if (hoveredVertex == vertex) hoveredVertex = null;
|
||||
logger.info("已移除一个控制点 (剩余: {})", orderedControlVertices.size());
|
||||
selectedVertices.remove(vertex);
|
||||
if (hoveredVertex == vertex) {
|
||||
hoveredVertex = null;
|
||||
}
|
||||
vertex.setTag(VertexTag.DEFORMATION);
|
||||
vertex.delete();
|
||||
updateDeformationRegion();
|
||||
}
|
||||
|
||||
@@ -316,7 +299,7 @@ public class VertexDeformationTool extends Tool {
|
||||
if (targetMesh == null) return null;
|
||||
float tolerance = VERTEX_TOLERANCE / calculateScaleFactor();
|
||||
float toleranceSq = tolerance * tolerance;
|
||||
for (Vertex v : orderedControlVertices) { // 优化:只在控制点中查找
|
||||
for (Vertex v : orderedControlVertices) {
|
||||
if (v.position.distanceSquared(x, y) < toleranceSq) {
|
||||
return v;
|
||||
}
|
||||
@@ -338,9 +321,9 @@ public class VertexDeformationTool extends Tool {
|
||||
private Cursor createVertexCursor() {
|
||||
int size = 32; BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = img.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
int center = size / 2; g2.setColor(Color.GREEN); g2.setStroke(new BasicStroke(2f));
|
||||
g2.drawOval(center - 6, center - 6, 12, 12); g2.dispose();
|
||||
return Toolkit.getDefaultToolkit().createCustomCursor(img, new Point(center, center), "VertexCursor");
|
||||
int center = size / 2; g2.setColor(Color.ORANGE); g2.setStroke(new BasicStroke(2f));
|
||||
g2.drawRect(center - 5, center - 5, 10, 10); g2.dispose();
|
||||
return Toolkit.getDefaultToolkit().createCustomCursor(img, new Point(center, center), "VertexSelectCursor");
|
||||
}
|
||||
|
||||
public Mesh2D getTargetMesh() {return targetMesh;}
|
||||
|
||||
@@ -312,7 +312,7 @@ public class FrameInterpolator {
|
||||
if (target.deleted) {
|
||||
// "删除"操作意味着取消其变形资格
|
||||
if (vertex.getTag() == VertexTag.DEFORMATION) {
|
||||
vertex.setTag(VertexTag.DEFAULT);
|
||||
vertex.setTag(VertexTag.DEFORMATION);
|
||||
meshModified = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -19,168 +19,12 @@ public class MeshTextureUtil {
|
||||
float h = img.getHeight();
|
||||
|
||||
try {
|
||||
Mesh2D o = Mesh2D.createQuad(meshName, w, h);
|
||||
return subdivideMeshForLiquify(o, 3);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
return createSubdividedQuad(meshName, w, h, 3);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return Mesh2D.createQuad(meshName, w, h);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
throw new RuntimeException("无法创建 Mesh2D");
|
||||
}
|
||||
|
||||
private static Mesh2D createSubdividedQuad(String name, float width, float height, int subdivisionLevel) {
|
||||
int segments = (int) Math.pow(2, subdivisionLevel);
|
||||
int vertexCount = (segments + 1) * (segments + 1);
|
||||
int triangleCount = segments * segments * 2;
|
||||
|
||||
float[] vertices = new float[vertexCount * 2];
|
||||
float[] uvs = new float[vertexCount * 2];
|
||||
int[] indices = new int[triangleCount * 3];
|
||||
|
||||
float halfW = width / 2f;
|
||||
float halfH = height / 2f;
|
||||
int vertexIndex = 0;
|
||||
for (int y = 0; y <= segments; y++) {
|
||||
for (int x = 0; x <= segments; x++) {
|
||||
float xPos = -halfW + (x * width) / segments;
|
||||
float yPos = -halfH + (y * height) / segments;
|
||||
|
||||
vertices[vertexIndex * 2] = xPos;
|
||||
vertices[vertexIndex * 2 + 1] = yPos;
|
||||
|
||||
uvs[vertexIndex * 2] = (float) x / segments;
|
||||
uvs[vertexIndex * 2 + 1] = 1f - (float) y / segments;
|
||||
|
||||
vertexIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (int y = 0; y < segments; y++) {
|
||||
for (int x = 0; x < segments; x++) {
|
||||
int topLeft = y * (segments + 1) + x;
|
||||
int topRight = topLeft + 1;
|
||||
int bottomLeft = (y + 1) * (segments + 1) + x;
|
||||
int bottomRight = bottomLeft + 1;
|
||||
|
||||
indices[index++] = topLeft;
|
||||
indices[index++] = topRight;
|
||||
indices[index++] = bottomLeft;
|
||||
|
||||
indices[index++] = topRight;
|
||||
indices[index++] = bottomRight;
|
||||
indices[index++] = bottomLeft;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Constructor<?> cons = null;
|
||||
for (Constructor<?> c : Mesh2D.class.getDeclaredConstructors()) {
|
||||
Class<?>[] params = c.getParameterTypes();
|
||||
if (params.length >= 4 && params[0] == String.class) {
|
||||
cons = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cons != null) {
|
||||
cons.setAccessible(true);
|
||||
Object meshObj = cons.newInstance(name, vertices, uvs, indices);
|
||||
if (meshObj instanceof Mesh2D mesh) {
|
||||
mesh.setPivot(0, 0);
|
||||
if (mesh.getOriginalPivot() != null) {
|
||||
mesh.setOriginalPivot(new Vector2f(0, 0));
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
throw new RuntimeException("无法创建细分网格");
|
||||
}
|
||||
|
||||
private static Mesh2D subdivideMeshForLiquify(Mesh2D originalMesh, int subdivisionLevel) {
|
||||
if (subdivisionLevel <= 0) return originalMesh;
|
||||
|
||||
try {
|
||||
float[] origVertices = originalMesh.getVertices();
|
||||
float[] origUVs = originalMesh.getUVs();
|
||||
int[] origIndices = originalMesh.getIndices();
|
||||
List<Vector2f> newVertices = new ArrayList<>();
|
||||
List<Vector2f> newUVs = new ArrayList<>();
|
||||
List<Integer> newIndices = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < origVertices.length / 2; i++) {
|
||||
newVertices.add(new Vector2f(origVertices[i * 2], origVertices[i * 2 + 1]));
|
||||
newUVs.add(new Vector2f(origUVs[i * 2], origUVs[i * 2 + 1]));
|
||||
}
|
||||
|
||||
for (int i = 0; i < origIndices.length; i += 3) {
|
||||
int i1 = origIndices[i];
|
||||
int i2 = origIndices[i + 1];
|
||||
int i3 = origIndices[i + 2];
|
||||
Vector2f v1 = newVertices.get(i1);
|
||||
Vector2f v2 = newVertices.get(i2);
|
||||
Vector2f v3 = newVertices.get(i3);
|
||||
Vector2f uv1 = newUVs.get(i1);
|
||||
Vector2f uv2 = newUVs.get(i2);
|
||||
Vector2f uv3 = newUVs.get(i3);
|
||||
|
||||
Vector2f mid12 = new Vector2f(v1).add(v2).mul(0.5f);
|
||||
Vector2f mid23 = new Vector2f(v2).add(v3).mul(0.5f);
|
||||
Vector2f mid31 = new Vector2f(v3).add(v1).mul(0.5f);
|
||||
Vector2f uvMid12 = new Vector2f(uv1).add(uv2).mul(0.5f);
|
||||
Vector2f uvMid23 = new Vector2f(uv2).add(uv3).mul(0.5f);
|
||||
Vector2f uvMid31 = new Vector2f(uv3).add(uv1).mul(0.5f);
|
||||
|
||||
int mid12Idx = newVertices.size();
|
||||
newVertices.add(mid12);
|
||||
newUVs.add(uvMid12);
|
||||
int mid23Idx = newVertices.size();
|
||||
newVertices.add(mid23);
|
||||
newUVs.add(uvMid23);
|
||||
int mid31Idx = newVertices.size();
|
||||
newVertices.add(mid31);
|
||||
newUVs.add(uvMid31);
|
||||
|
||||
newIndices.add(i1); newIndices.add(mid12Idx); newIndices.add(mid31Idx);
|
||||
newIndices.add(i2); newIndices.add(mid23Idx); newIndices.add(mid12Idx);
|
||||
newIndices.add(i3); newIndices.add(mid31Idx); newIndices.add(mid23Idx);
|
||||
newIndices.add(mid12Idx); newIndices.add(mid23Idx); newIndices.add(mid31Idx);
|
||||
}
|
||||
|
||||
float[] finalVertices = new float[newVertices.size() * 2];
|
||||
float[] finalUVs = new float[newUVs.size() * 2];
|
||||
int[] finalIndices = new int[newIndices.size()];
|
||||
|
||||
for (int i = 0; i < newVertices.size(); i++) {
|
||||
finalVertices[i * 2] = newVertices.get(i).x;
|
||||
finalVertices[i * 2 + 1] = newVertices.get(i).y;
|
||||
finalUVs[i * 2] = newUVs.get(i).x;
|
||||
finalUVs[i * 2 + 1] = newUVs.get(i).y;
|
||||
}
|
||||
|
||||
for (int i = 0; i < newIndices.size(); i++) {
|
||||
finalIndices[i] = newIndices.get(i);
|
||||
}
|
||||
|
||||
Mesh2D subdividedMesh = originalMesh.copy();
|
||||
subdividedMesh.setMeshData(finalVertices, finalUVs, finalIndices);
|
||||
|
||||
if (subdivisionLevel > 1) {
|
||||
return subdivideMeshForLiquify(subdividedMesh, subdivisionLevel - 1);
|
||||
}
|
||||
return subdividedMesh;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return originalMesh;
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture tryCreateTextureFromImageMemory(BufferedImage img, String texName) {
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1612,7 +1612,6 @@ public class ModelPart {
|
||||
Vector2f worldPoint = Matrix3fUtils.transformPoint(this.worldTransform, localPoint);
|
||||
vertex.position.set(worldPoint);
|
||||
}
|
||||
mesh.applyDeformation();
|
||||
mesh.markDirty();
|
||||
}
|
||||
for (ModelPart child : children) {
|
||||
|
||||
@@ -16,6 +16,9 @@ public class Vertex {
|
||||
public Vector2f originalPosition; // 原始顶点位置 (用于变形)
|
||||
private VertexTag tag;
|
||||
private boolean selected;
|
||||
private String name;
|
||||
private boolean isDelete;
|
||||
public int index;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
@@ -45,6 +48,13 @@ public class Vertex {
|
||||
this.tag = VertexTag.DEFAULT;
|
||||
}
|
||||
|
||||
public Vertex(Vector2f position, Vector2f uv, VertexTag tag) {
|
||||
this.position = new Vector2f(position);
|
||||
this.uv = new Vector2f(uv);
|
||||
this.originalPosition = new Vector2f(position);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数(用于复制)
|
||||
*
|
||||
@@ -59,6 +69,20 @@ public class Vertex {
|
||||
this.tag = VertexTag.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷构造函数,用于仅通过位置创建顶点
|
||||
* UV坐标将默认为 (0, 0)。
|
||||
*
|
||||
* @param x 顶点 x 坐标
|
||||
* @param y 顶点 y 坐标
|
||||
*/
|
||||
public Vertex(float x, float y) {
|
||||
this.position = new Vector2f(x, y);
|
||||
this.uv = new Vector2f(0, 0); // 为UV坐标提供一个默认值
|
||||
this.originalPosition = new Vector2f(x, y);
|
||||
this.tag = VertexTag.DEFAULT;
|
||||
}
|
||||
|
||||
public VertexTag getTag() {
|
||||
return tag;
|
||||
}
|
||||
@@ -67,6 +91,18 @@ public class Vertex {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public void delete(){
|
||||
isDelete = true;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置为原始位置
|
||||
*/
|
||||
@@ -97,6 +133,10 @@ public class Vertex {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public boolean isDelete() {
|
||||
return isDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建此顶点的深拷贝
|
||||
*/
|
||||
@@ -109,22 +149,38 @@ public class Vertex {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Vertex vertex = (Vertex) o;
|
||||
return Objects.equals(position, vertex.position) &&
|
||||
return selected == vertex.selected &&
|
||||
isDelete == vertex.isDelete &&
|
||||
Objects.equals(position, vertex.position) &&
|
||||
Objects.equals(uv, vertex.uv) &&
|
||||
Objects.equals(originalPosition, vertex.originalPosition);
|
||||
Objects.equals(originalPosition, vertex.originalPosition) &&
|
||||
tag == vertex.tag &&
|
||||
Objects.equals(name, vertex.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(position, uv, originalPosition);
|
||||
return Objects.hash(position, uv, originalPosition, tag, selected, name, isDelete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vertex{" +
|
||||
"pos=" + position +
|
||||
"name='" + name + '\'' +
|
||||
", pos=" + position +
|
||||
", uv=" + uv +
|
||||
", orig=" + originalPosition +
|
||||
", tag=" + tag +
|
||||
", selected=" + selected +
|
||||
", isDelete=" + isDelete +
|
||||
'}';
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,11 @@ package com.chuangzhou.vivid2D.render.model.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 一个带有名称和标签的 Vertex 集合。
|
||||
* 一个自包含的几何数据单元,用于管理顶点(vertices)
|
||||
* 以及定义它们之间拓扑结构(三角形)的索引(indices)。
|
||||
*
|
||||
* @author Gemini
|
||||
*/
|
||||
@@ -17,79 +14,115 @@ public class VertexList implements Iterable<Vertex> {
|
||||
|
||||
public final List<Vertex> vertices;
|
||||
private String name;
|
||||
private int[] indices;
|
||||
|
||||
/**
|
||||
* 默认构造函数。
|
||||
* 名称默认为 "KongFuZi",标签默认为 "DEFAULT"。
|
||||
*/
|
||||
public VertexList() {
|
||||
this.vertices = new ArrayList<>();
|
||||
this.name = "KongFuZi";
|
||||
this.indices = new int[0]; // 初始化为空数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param name 此列表的名称
|
||||
* @param tag 此列表的标签 (VertexTag)
|
||||
*/
|
||||
public VertexList(String name) {
|
||||
this.vertices = new ArrayList<>();
|
||||
this.name = Objects.requireNonNull(name, "Name cannot be null");
|
||||
this.indices = new int[0]; // 初始化为空数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param name 此列表的名称
|
||||
* @param tag 此列表的标签
|
||||
* @param name 此列表的名称
|
||||
* @param initialVertices 用于初始化列表的顶点集合
|
||||
* @param initialIndices 用于初始化列表的索引集合
|
||||
*/
|
||||
public VertexList(String name, Collection<Vertex> initialVertices) {
|
||||
public VertexList(String name, Collection<Vertex> initialVertices, int[] initialIndices) {
|
||||
this(name);
|
||||
if (initialVertices != null) {
|
||||
this.vertices.addAll(initialVertices);
|
||||
}
|
||||
setIndices(initialIndices); // 使用setter来安全地设置初始索引
|
||||
}
|
||||
|
||||
// --- 列表管理 ---
|
||||
// --- 列表管理 (已更新以支持索引) ---
|
||||
|
||||
/**
|
||||
* 向列表末尾添加一个顶点。
|
||||
* 注意:这不会自动更新索引。您需要手动调用 setIndices() 来使用这个新顶点。
|
||||
*
|
||||
* @param vertex 要添加的顶点
|
||||
*/
|
||||
public void add(Vertex vertex) {
|
||||
if (vertex != null) {
|
||||
vertex.setIndex(this.vertices.size());
|
||||
this.vertices.add(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除列表中的指定顶点。
|
||||
* 移除列表中的指定顶点,并安全地移除所有引用它的三角形,同时重映射所有后续索引。
|
||||
*
|
||||
* @param vertex 要移除的顶点
|
||||
* @return 如果成功移除则为 true
|
||||
*/
|
||||
public boolean remove(Vertex vertex) {
|
||||
return this.vertices.remove(vertex);
|
||||
public boolean remove(Object vertex) {
|
||||
int index = this.vertices.indexOf(vertex);
|
||||
if (index != -1) {
|
||||
remove(index); // 委托给基于索引的移除方法
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定索引处的顶点。
|
||||
* [已重构] 移除指定索引处的顶点,并安全地移除所有引用它的三角形,同时重映射所有后续索引。
|
||||
*
|
||||
* @param index 要移除的索引
|
||||
* @return 被移除的顶点
|
||||
*/
|
||||
public Vertex remove(int index) {
|
||||
if (index < 0 || index >= this.vertices.size()) {
|
||||
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.vertices.size());
|
||||
}
|
||||
|
||||
// 核心逻辑:在移除顶点前,必须重构索引数组
|
||||
List<Integer> newIndicesList = new ArrayList<>();
|
||||
for (int i = 0; i < this.indices.length; i += 3) {
|
||||
int i1 = this.indices[i];
|
||||
int i2 = this.indices[i + 1];
|
||||
int i3 = this.indices[i + 2];
|
||||
|
||||
// 如果三角形包含被删除的顶点,则整个三角形都应该被丢弃
|
||||
if (i1 == index || i2 == index || i3 == index) {
|
||||
continue; // 跳过这个三角形
|
||||
}
|
||||
|
||||
// 调整索引编号:任何大于被删除索引的索引都需要减一
|
||||
if (i1 > index) i1--;
|
||||
if (i2 > index) i2--;
|
||||
if (i3 > index) i3--;
|
||||
|
||||
newIndicesList.add(i1);
|
||||
newIndicesList.add(i2);
|
||||
newIndicesList.add(i3);
|
||||
}
|
||||
|
||||
// 用重构后的新索引数组替换旧的
|
||||
this.indices = newIndicesList.stream().mapToInt(Integer::intValue).toArray();
|
||||
|
||||
// 最后,安全地从列表中移除顶点
|
||||
return this.vertices.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定索引处的顶点。
|
||||
*
|
||||
* @param index 索引
|
||||
* @return 顶点
|
||||
*/
|
||||
public Vertex get(int index) {
|
||||
return this.vertices.get(index);
|
||||
@@ -97,8 +130,6 @@ public class VertexList implements Iterable<Vertex> {
|
||||
|
||||
/**
|
||||
* 返回列表中的顶点数量。
|
||||
*
|
||||
* @return 顶点数量
|
||||
*/
|
||||
public int size() {
|
||||
return this.vertices.size();
|
||||
@@ -106,53 +137,124 @@ public class VertexList implements Iterable<Vertex> {
|
||||
|
||||
/**
|
||||
* 检查列表是否为空。
|
||||
*
|
||||
* @return 如果为空则为 true
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.vertices.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空列表中的所有顶点。
|
||||
* [已重构] 清空列表中的所有顶点和索引。
|
||||
*/
|
||||
public void clear() {
|
||||
this.vertices.clear();
|
||||
this.indices = new int[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回内部列表的只读视图。
|
||||
* (注意:如果需要修改,请使用 add/remove 等方法)
|
||||
* 返回内部顶点列表的副本。
|
||||
*
|
||||
* @return 顶点的列表
|
||||
* @return 顶点的列表副本
|
||||
*/
|
||||
public List<Vertex> getVertices() {
|
||||
return new ArrayList<>(vertices); // 返回一个副本以防止外部修改
|
||||
return new ArrayList<>(vertices);
|
||||
}
|
||||
|
||||
// --- Getter 和 Setter ---
|
||||
|
||||
/**
|
||||
* 获取此列表的名称。
|
||||
* 根据标签过滤并返回顶点列表。
|
||||
*
|
||||
* @return 过滤后的顶点列表
|
||||
*/
|
||||
public List<Vertex> getVertices(VertexTag tag) {
|
||||
return vertices.stream()
|
||||
.filter(vertex -> vertex.getTag() == tag)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// --- Getter 和 Setter (已更新以支持索引) ---
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此列表的名称。
|
||||
*
|
||||
* @param name 新名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = Objects.requireNonNull(name, "Name cannot be null");
|
||||
}
|
||||
|
||||
// --- 迭代器 ---
|
||||
/**
|
||||
* [新增] 获取索引数组的副本。
|
||||
*
|
||||
* @return 索引数组的克隆
|
||||
*/
|
||||
public int[] getIndices() {
|
||||
return indices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回顶点的迭代器。
|
||||
* [新增] 获取仅由具有指定标签(Tag)的顶点组成的三角形索引。
|
||||
*
|
||||
* <p>此方法会执行以下操作:
|
||||
* <ol>
|
||||
* <li>筛选出所有具有指定 {@code tag} 的顶点。</li>
|
||||
* <li>遍历原始索引数组中的所有三角形。</li>
|
||||
* <li>如果一个三角形的【全部三个顶点】都符合指定的 {@code tag},则保留该三角形。</li>
|
||||
* <li>将这些保留下来的三角形的索引,重映射到仅包含筛选后顶点的新索引空间中。</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param tag 要筛选的顶点标签
|
||||
* @return 一个新的索引数组,其中的索引值对应于通过 {@code getVertices(tag)} 获取的顶点列表。
|
||||
*/
|
||||
public int[] getIndices(VertexTag tag) {
|
||||
if (tag == null) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
// 1. 快速找到所有带标签的顶点的原始索引,存入一个Set以便快速查找
|
||||
Set<Integer> taggedOriginalIndices = new HashSet<>();
|
||||
for (int i = 0; i < this.vertices.size(); i++) {
|
||||
if (this.vertices.get(i).getTag() == tag) {
|
||||
taggedOriginalIndices.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何顶点带此标签,则不可能有相关三角形
|
||||
if (taggedOriginalIndices.isEmpty()) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
// 2. 遍历所有三角形,只要有一个顶点的索引在Set中,就保留该三角形
|
||||
List<Integer> newIndices = new ArrayList<>();
|
||||
for (int i = 0; i < this.indices.length; i += 3) {
|
||||
int i1 = this.indices[i];
|
||||
int i2 = this.indices[i + 1];
|
||||
int i3 = this.indices[i + 2];
|
||||
|
||||
// 核心逻辑修改:从 && (AND) 改为 || (OR)
|
||||
if (taggedOriginalIndices.contains(i1) ||
|
||||
taggedOriginalIndices.contains(i2) ||
|
||||
taggedOriginalIndices.contains(i3)) {
|
||||
|
||||
// 添加原始索引,因为它们将用于原始的、完整的顶点列表
|
||||
newIndices.add(i1);
|
||||
newIndices.add(i2);
|
||||
newIndices.add(i3);
|
||||
}
|
||||
}
|
||||
|
||||
return newIndices.stream().mapToInt(Integer::intValue).toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* [新增] 设置索引数组。
|
||||
*
|
||||
* @param indices 新的索引数组
|
||||
*/
|
||||
public void setIndices(int[] indices) {
|
||||
this.indices = (indices != null) ? indices.clone() : new int[0];
|
||||
}
|
||||
|
||||
|
||||
// --- 迭代器 ---
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<Vertex> iterator() {
|
||||
return this.vertices.iterator();
|
||||
@@ -165,6 +267,7 @@ public class VertexList implements Iterable<Vertex> {
|
||||
return "VertexList{" +
|
||||
"name='" + name + '\'' +
|
||||
", vertexCount=" + vertices.size() +
|
||||
", indexCount=" + indices.length +
|
||||
'}';
|
||||
}
|
||||
|
||||
@@ -174,12 +277,15 @@ public class VertexList implements Iterable<Vertex> {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
VertexList that = (VertexList) o;
|
||||
return Objects.equals(vertices, that.vertices) &&
|
||||
Objects.equals(name, that.name);
|
||||
Objects.equals(name, that.name) &&
|
||||
Arrays.equals(indices, that.indices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(vertices, name);
|
||||
int result = Objects.hash(vertices, name);
|
||||
result = 31 * result + Arrays.hashCode(indices);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void set(int index, Vertex vertex) {
|
||||
|
||||
@@ -11,21 +11,24 @@ import org.joml.Vector2f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* [MODIFIED] 顶点变形渲染工具
|
||||
* 该类现在渲染 Mesh2D 中被标记为 VertexTag.DEFORMATION 的一级顶点。
|
||||
* [已修改] 顶点变形渲染工具 (支持外部控制笼)
|
||||
* 该类现在渲染:
|
||||
* 1. 底层被变形网格的【三角剖分线框】。
|
||||
* 2. 位于上层的【外部控制笼】(控制点和连线)。
|
||||
* 这样可以直观地展示控制笼如何影响网格变形。
|
||||
*/
|
||||
public class VertexDeformationRander extends RanderTools {
|
||||
|
||||
// [NEW] 定义可变形顶点的默认渲染大小
|
||||
private static final float DEFORMATION_VERTEX_SIZE = 8.0f;
|
||||
private static final float DEFORMATION_VERTEX_SIZE = 6.0f;
|
||||
|
||||
@Override
|
||||
public void init(Map<String, Boolean> algorithmEnabled) {
|
||||
// [MODIFIED] 更新算法名称以反映新功能
|
||||
algorithmEnabled.put("showDeformationVertices", false);
|
||||
algorithmEnabled.put("showDeformationVertexInfluence", true);
|
||||
}
|
||||
@@ -33,9 +36,14 @@ public class VertexDeformationRander extends RanderTools {
|
||||
@Override
|
||||
public boolean render(Matrix3f modelMatrix, Object renderContext) {
|
||||
if (renderContext instanceof Mesh2D mesh2D) {
|
||||
// [MODIFIED] 检查新的算法开关并调用新的渲染方法
|
||||
if (mesh2D.getStates("showDeformationVertices")) {
|
||||
drawDeformationVertices(mesh2D);
|
||||
RenderSystem.pushState();
|
||||
try {
|
||||
mesh2D.setSolidShader(modelMatrix);
|
||||
drawControlCageAndMesh(mesh2D);
|
||||
} finally {
|
||||
RenderSystem.popState();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -43,44 +51,28 @@ public class VertexDeformationRander extends RanderTools {
|
||||
}
|
||||
|
||||
/**
|
||||
* [MODIFIED] 重写此方法以渲染被标记为 DEFORMATION 的一级顶点。
|
||||
* @param mesh2D 要渲染的网格
|
||||
* [已修改] 此方法现在会先渲染底层网格的线框,然后再渲染上层的控制笼。
|
||||
* @param mesh2D 要渲染的网格 (作为被变形的对象)
|
||||
*/
|
||||
private void drawDeformationVertices(Mesh2D mesh2D) {
|
||||
// 1. 筛选出所有可变形的顶点
|
||||
List<Vertex> deformationVertices = mesh2D.getActiveVertexList().vertices.stream()
|
||||
.filter(v -> v.getTag() == VertexTag.DEFORMATION)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (deformationVertices.isEmpty()) {
|
||||
return; // 没有可变形顶点,无需渲染
|
||||
}
|
||||
|
||||
private void drawControlCageAndMesh(Mesh2D mesh2D) {
|
||||
List<Vertex> controlCage = mesh2D.getDeformationControlVertices();
|
||||
Tesselator t = Tesselator.getInstance();
|
||||
BufferBuilder bb = t.getBuilder();
|
||||
|
||||
RenderSystem.pushState();
|
||||
try {
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
// 2. 绘制连接线
|
||||
drawConnectionLines(bb, deformationVertices);
|
||||
|
||||
// 3. 绘制每个可变形顶点
|
||||
for (Vertex vertex : deformationVertices) {
|
||||
Vector2f p = vertex.position;
|
||||
|
||||
// 定义颜色
|
||||
Vector4f centerCol = new Vector4f(0.1f, 0.9f, 0.2f, 0.7f); // 中心亮绿色
|
||||
Vector4f outerCol = new Vector4f(0.0f, 0.5f, 0.1f, 0.3f); // 外部深绿色
|
||||
Vector4f outlineCol = new Vector4f(1.0f, 1.0f, 1.0f, 0.9f); // 白色轮廓
|
||||
|
||||
// 绘制顶点视觉效果
|
||||
drawCircleGradient(bb, p.x, p.y, DEFORMATION_VERTEX_SIZE, centerCol, outerCol, 16);
|
||||
drawCircleOutline(bb, p.x, p.y, DEFORMATION_VERTEX_SIZE, outlineCol, 16);
|
||||
drawCircleOutline(bb, p.x, p.y, DEFORMATION_VERTEX_SIZE * 0.5f, new Vector4f(1.0f, 1.0f, 1.0f, 0.5f), 12);
|
||||
drawMeshWireframe(bb, mesh2D);
|
||||
if (controlCage != null && !controlCage.isEmpty()) {
|
||||
List<Vertex> orderedCage = getOrderedVertices(controlCage);
|
||||
drawConnectionLines(bb, orderedCage);
|
||||
for (Vertex vertex : orderedCage) {
|
||||
if (vertex == null){
|
||||
continue;
|
||||
}
|
||||
drawStyledVertex(bb, vertex.position.x, vertex.position.y, vertex.isSelected());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
RenderSystem.popState();
|
||||
@@ -88,127 +80,113 @@ public class VertexDeformationRander extends RanderTools {
|
||||
}
|
||||
|
||||
/**
|
||||
* [MODIFIED] 此方法现在接收一个 List<Vertex>
|
||||
* 绘制连接可变形顶点的线。
|
||||
* 绘制网格的线框,以显示其三角剖分结构。
|
||||
* 这就是“Vertex 的控制区域”的可视化。
|
||||
* @param bb BufferBuilder 实例
|
||||
* @param mesh 目标网格
|
||||
*/
|
||||
private void drawConnectionLines(BufferBuilder bb, List<Vertex> verts) {
|
||||
if (verts.size() < 2) return;
|
||||
|
||||
// 为了美观,我们对顶点进行排序以绘制一个简单的多边形轮廓
|
||||
List<Vector2f> positions = verts.stream().map(v -> v.position).collect(Collectors.toList());
|
||||
List<Vector2f> orderedPositions = buildOrderedPolygon(positions);
|
||||
|
||||
if (orderedPositions.size() < 2) return;
|
||||
|
||||
GL11.glLineWidth(1.5f);
|
||||
bb.begin(GL11.GL_LINE_LOOP, orderedPositions.size());
|
||||
bb.setColor(new Vector4f(1f, 1f, 1f, 0.25f));
|
||||
for (Vector2f p : orderedPositions) {
|
||||
bb.vertex(p.x, p.y, 0f, 0f);
|
||||
private void drawMeshWireframe(BufferBuilder bb, Mesh2D mesh) {
|
||||
List<Vertex> vertices = mesh.getActiveVertexList().getVertices();
|
||||
int[] indices = mesh.getActiveVertexList().getIndices(VertexTag.DEFORMATION);
|
||||
if (vertices == null || vertices.isEmpty() || indices == null || indices.length == 0) {
|
||||
return;
|
||||
}
|
||||
bb.endImmediate();
|
||||
Vector4f wireframeColor = new Vector4f(0.5f, 0.5f, 0.5f, 0.4f);
|
||||
GL11.glLineWidth(1.0f);
|
||||
|
||||
for (int i = 0; i < orderedPositions.size(); i++) {
|
||||
Vector2f a = orderedPositions.get(i);
|
||||
Vector2f b = orderedPositions.get((i + 1) % orderedPositions.size());
|
||||
drawDashedLine(bb, a.x, a.y, b.x, b.y, 6, 4, new Vector4f(1f,1f,1f,0.1f));
|
||||
for (int i = 0; i < indices.length; i += 3) {
|
||||
int i1 = indices[i];
|
||||
int i2 = indices[i + 1];
|
||||
int i3 = indices[i + 2];
|
||||
Vertex v1 = vertices.get(i1);
|
||||
Vertex v2 = vertices.get(i2);
|
||||
Vertex v3 = vertices.get(i3);
|
||||
bb.begin(GL11.GL_LINE_LOOP, 3);
|
||||
bb.setColor(wireframeColor);
|
||||
bb.vertex(v1.position.x, v1.position.y, 0f, 0f);
|
||||
bb.vertex(v2.position.x, v2.position.y, 0f, 0f);
|
||||
bb.vertex(v3.position.x, v3.position.y, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据输入点(任意顺序),生成一个“简单多边形”顺序:
|
||||
* - 先计算重心
|
||||
* - 按相对于重心的 atan2 排序(极角排序)
|
||||
* 绘制连接控制顶点的虚线。
|
||||
*/
|
||||
private java.util.List<Vector2f> buildOrderedPolygon(java.util.List<Vector2f> pts) {
|
||||
if (pts == null || pts.size() <= 1) return new ArrayList<>(pts);
|
||||
private void drawConnectionLines(BufferBuilder bb, List<Vertex> verts) {
|
||||
if (verts == null || verts.size() < 2) return;
|
||||
Vector4f lineColor = new Vector4f(0.1f, 0.1f, 0.1f, 0.85f);
|
||||
|
||||
// 计算重心
|
||||
Vector2f cen = new Vector2f(0f, 0f);
|
||||
for (Vector2f v : pts) { cen.add(v); }
|
||||
cen.div(pts.size());
|
||||
|
||||
// 创建副本并按角度排序
|
||||
List<Vector2f> sortedPts = new ArrayList<>(pts);
|
||||
sortedPts.sort(Comparator.comparingDouble(p -> Math.atan2(p.y - cen.y, p.x - cen.x)));
|
||||
|
||||
return sortedPts;
|
||||
for (int i = 0; i < verts.size(); i++) {
|
||||
Vector2f a = verts.get(i).position;
|
||||
Vector2f b = verts.get((i + 1) % verts.size()).position; // 使用 % 来处理首尾相连
|
||||
drawDashedLine(bb, a.x, a.y, b.x, b.y, 4, 4, lineColor);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 以下是无需修改的通用绘图和几何辅助方法 ---
|
||||
/**
|
||||
* 绘制自定义样式的顶点,并根据 'isSelected' 状态改变颜色。
|
||||
*/
|
||||
private void drawStyledVertex(BufferBuilder bb, float cx, float cy, boolean isSelected) {
|
||||
float halfSize = VertexDeformationRander.DEFORMATION_VERTEX_SIZE / 2.0f;
|
||||
Vector4f fillColor = isSelected ? new Vector4f(1.0f, 0.3f, 0.3f, 1.0f) : new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
bb.begin(GL11.GL_QUADS, 4);
|
||||
bb.setColor(fillColor);
|
||||
bb.vertex(cx - halfSize, cy - halfSize, 0f, 0f);
|
||||
bb.vertex(cx + halfSize, cy - halfSize, 0f, 0f);
|
||||
bb.vertex(cx + halfSize, cy + halfSize, 0f, 0f);
|
||||
bb.vertex(cx - halfSize, cy + halfSize, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
|
||||
GL11.glLineWidth(1.5f);
|
||||
bb.begin(GL11.GL_LINE_LOOP, 4);
|
||||
bb.setColor(new Vector4f(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
bb.vertex(cx - halfSize, cy - halfSize, 0f, 0f);
|
||||
bb.vertex(cx + halfSize, cy - halfSize, 0f, 0f);
|
||||
bb.vertex(cx + halfSize, cy + halfSize, 0f, 0f);
|
||||
bb.vertex(cx - halfSize, cy + halfSize, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
GL11.glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法,获取按极角排序后的顶点列表 (Vertex 对象)。
|
||||
*/
|
||||
private List<Vertex> getOrderedVertices(List<Vertex> verts) {
|
||||
if (verts == null || verts.size() <= 1) return new ArrayList<>(verts);
|
||||
|
||||
Vector2f center = new Vector2f(0f, 0f);
|
||||
for (Vertex v : verts) { center.add(v.position); }
|
||||
center.div(verts.size());
|
||||
|
||||
List<Vertex> sortedVerts = new ArrayList<>(verts);
|
||||
sortedVerts.sort(Comparator.comparingDouble(v -> Math.atan2(v.position.y - center.y, v.position.x - center.x)));
|
||||
return sortedVerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制虚线的底层实现。
|
||||
*/
|
||||
private void drawDashedLine(BufferBuilder bb, float x1, float y1, float x2, float y2, float segmentLen, float gapLen, Vector4f color) {
|
||||
float dx = x2 - x1;
|
||||
float dy = y2 - y1;
|
||||
float dx = x2 - x1, dy = y2 - y1;
|
||||
float total = (float)Math.sqrt(dx*dx + dy*dy);
|
||||
if (total < 1e-4f) return;
|
||||
float nx = dx / total;
|
||||
float ny = dy / total;
|
||||
|
||||
float nx = dx / total, ny = dy / total;
|
||||
float pos = 0f;
|
||||
GL11.glLineWidth(1.5f); // 让虚线稍微粗一点
|
||||
while (pos < total) {
|
||||
float segStart = pos;
|
||||
float segEnd = Math.min(total, pos + segmentLen);
|
||||
if (segEnd > segStart) {
|
||||
float sx = x1 + nx * segStart;
|
||||
float sy = y1 + ny * segStart;
|
||||
float ex = x1 + nx * segEnd;
|
||||
float ey = y1 + ny * segEnd;
|
||||
float sx = x1 + nx * segStart, sy = y1 + ny * segStart;
|
||||
float ex = x1 + nx * segEnd, ey = y1 + ny * segEnd;
|
||||
bb.begin(GL11.GL_LINES, 2);
|
||||
bb.setColor(color);
|
||||
bb.vertex(sx, sy, 0f, 0f);
|
||||
bb.vertex(ex, ey, 0f, 0f);
|
||||
bb.vertex(sx, sy, 0f, 0f); bb.vertex(ex, ey, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
}
|
||||
pos += segmentLen + gapLen;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCircleSolid(BufferBuilder bb, float cx, float cy, float radius, Vector4f color, int segments) {
|
||||
if (radius <= 0f) return;
|
||||
segments = Math.max(6, segments);
|
||||
bb.begin(GL11.GL_TRIANGLE_FAN, segments + 2);
|
||||
bb.setColor(color);
|
||||
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(color);
|
||||
bb.vertex(x, y, 0f, 0f);
|
||||
}
|
||||
bb.endImmediate();
|
||||
}
|
||||
|
||||
private void drawCircleGradient(BufferBuilder bb, float cx, float cy, float radius, Vector4f centerColor, Vector4f outerColor, int segments) {
|
||||
if (radius <= 0f) return;
|
||||
segments = Math.max(8, segments);
|
||||
bb.begin(GL11.GL_TRIANGLE_FAN, segments + 2);
|
||||
bb.setColor(centerColor);
|
||||
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(outerColor);
|
||||
bb.vertex(x, y, 0f, 0f);
|
||||
}
|
||||
bb.endImmediate();
|
||||
}
|
||||
|
||||
private void drawCircleOutline(BufferBuilder bb, float cx, float cy, float radius, Vector4f color, int segments) {
|
||||
if (radius <= 0f) return;
|
||||
segments = Math.max(8, segments);
|
||||
bb.begin(GL11.GL_LINE_LOOP, segments);
|
||||
bb.setColor(color);
|
||||
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();
|
||||
GL11.glLineWidth(1.0f);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user