feat(render): 添加网格中心点和旋转功能支持

- 为 Mesh2D 类添加 pivot 属性及对应的 getter/setter 方法
- 实现中心点和旋转手柄的可视化绘制逻辑
- 在 ModelRenderPanel 中新增旋转和移动中心点的交互模式
- 支持通过拖拽调整网格的中心点位置- 支持围绕自定义中心点进行旋转操作
- 更新 Mesh2D 的 copy、equals 和 hashCode 方法以包含 pivot 信息-优化选中网格的显示效果,添加多层边框和辅助标记
-修复 ModelPart 中设置中心点时的顶点坐标计算问题

(注意是测试版)
This commit is contained in:
tzdwindows 7
2025-10-17 21:28:25 +08:00
parent 879069a9f4
commit b3c50ca794
4 changed files with 294 additions and 54 deletions

View File

@@ -265,6 +265,7 @@ public class ModelLayerPanel extends JPanel {
// 先创建部件与 Mesh基于图片尺寸
ModelPart part = model.createPart(name);
part.setPivot(0,0);
Mesh2D mesh = createQuadForImage(img, name + "_mesh");
part.addMesh(mesh);

View File

@@ -79,7 +79,9 @@ public class ModelRenderPanel extends JPanel {
RESIZE_TOP_LEFT, // 调整左上角
RESIZE_TOP_RIGHT, // 调整右上角
RESIZE_BOTTOM_LEFT, // 调整左下角
RESIZE_BOTTOM_RIGHT // 调整右下角
RESIZE_BOTTOM_RIGHT, // 调整右下角
ROTATE, // 新增:旋转
MOVE_PIVOT // 新增:移动中心点
}
// 新增:拖拽相关字段
@@ -102,7 +104,10 @@ public class ModelRenderPanel extends JPanel {
private static final float ZOOM_MIN = 0.1f;
private static final float ZOOM_MAX = 8.0f;
private volatile boolean shiftDuringDrag = false;
private volatile float rotationStartAngle = 0.0f;
private volatile float partInitialRotation = 0.0f;
private volatile Vector2f rotationCenter = new Vector2f();
private static final float ROTATION_HANDLE_DISTANCE = 30.0f;
/**
* 构造函数:使用模型路径
*/
@@ -298,7 +303,42 @@ public class ModelRenderPanel extends JPanel {
// 首先检查是否点击了选择框的调整手柄
DragMode dragMode = checkResizeHandleHit(modelX, modelY);
if (dragMode != DragMode.NONE && selectedMesh != null) {
if (dragMode == DragMode.ROTATE && selectedMesh != null) {
// 开始旋转
currentDragMode = DragMode.ROTATE;
dragStartX = modelX;
dragStartY = modelY;
// 获取边界框和中心点
BoundingBox bounds = selectedMesh.getBounds();
rotationCenter.set((bounds.getMinX() + bounds.getMaxX()) / 2.0f,
(bounds.getMinY() + bounds.getMaxY()) / 2.0f);
// 计算初始角度
rotationStartAngle = (float) Math.atan2(dragStartY - rotationCenter.y,
dragStartX - rotationCenter.x);
// 记录部件的初始旋转
ModelPart selPart = findPartByMesh(selectedMesh);
if (selPart != null) {
partInitialRotation = selPart.getRotation();
}
logger.info("开始旋转,中心点: ({}, {})", rotationCenter.x, rotationCenter.y);
}else if (dragMode == DragMode.MOVE_PIVOT && selectedMesh != null) {
// 开始移动中心点
currentDragMode = DragMode.MOVE_PIVOT;
dragStartX = modelX;
dragStartY = modelY;
// 记录初始中心点位置
BoundingBox bounds = selectedMesh.getBounds();
rotationCenter.set((bounds.getMinX() + bounds.getMaxX()) / 2.0f,
(bounds.getMinY() + bounds.getMaxY()) / 2.0f);
logger.info("开始移动中心点");
} else if (dragMode != DragMode.NONE && selectedMesh != null) {
// 开始调整大小
currentDragMode = dragMode;
dragStartX = modelX; // 记录拖拽起始位置
@@ -363,13 +403,25 @@ public class ModelRenderPanel extends JPanel {
float maxX = bounds.getMaxX();
float maxY = bounds.getMaxY();
// 使用 Mesh2D 的实际中心点,而不是边界框中心
Vector2f actualPivot = selectedMesh.getPivot();
float centerX = actualPivot.x;
float centerY = actualPivot.y;
// 动态计算检测阈值,基于面板缩放比例
float scaleFactor = calculateScaleFactor();
float borderThickness = BORDER_THICKNESS / scaleFactor;
float cornerSize = CORNER_SIZE / scaleFactor;
//logger.info("检测阈值 - 缩放因子: {}, 边框: {}, 角点: {}",
// scaleFactor, borderThickness, cornerSize);
// 首先检查是否点击了中心点(移动中心点)
if (isPointInCenterHandle(modelX, modelY, centerX, centerY, cornerSize)) {
return DragMode.MOVE_PIVOT;
}
// 检查是否点击了旋转手柄
if (isPointInRotationHandle(modelX, modelY, centerX, centerY, minY, cornerSize)) {
return DragMode.ROTATE;
}
// 扩展边界以包含调整手柄区域
float expandedMinX = minX - borderThickness;
@@ -402,6 +454,24 @@ public class ModelRenderPanel extends JPanel {
return DragMode.NONE;
}
/**
* 检查点是否在中心点区域内
*/
private boolean isPointInCenterHandle(float x, float y, float centerX, float centerY, float handleSize) {
return Math.abs(x - centerX) <= handleSize && Math.abs(y - centerY) <= handleSize;
}
/**
* 检查点是否在旋转手柄区域内
*/
private boolean isPointInRotationHandle(float x, float y, float centerX, float centerY, float topY, float handleSize) {
// 旋转手柄位于边界框上方一定距离
float rotationHandleY = topY - ROTATION_HANDLE_DISTANCE / calculateScaleFactor();
float rotationHandleX = centerX;
return Math.abs(x - rotationHandleX) <= handleSize && Math.abs(y - rotationHandleY) <= handleSize;
}
/**
* 计算当前缩放因子(模型单位与屏幕像素的比例)
*/
@@ -455,12 +525,20 @@ public class ModelRenderPanel extends JPanel {
float modelX = modelCoords[0];
float modelY = modelCoords[1];
if (currentDragMode == DragMode.MOVE) {
// 原有的移动逻辑
handleMoveDrag(modelX, modelY);
} else {
// 新的调整大小逻辑
handleResizeDrag(modelX, modelY);
switch (currentDragMode) {
case MOVE:
handleMoveDrag(modelX, modelY);
break;
case ROTATE:
handleRotateDrag(modelX, modelY);
break;
case MOVE_PIVOT:
handleMovePivotDrag(modelX, modelY);
break;
default:
// 调整大小逻辑
handleResizeDrag(modelX, modelY);
break;
}
} catch (Exception ex) {
@@ -469,6 +547,54 @@ public class ModelRenderPanel extends JPanel {
});
}
/**
* 处理旋转拖拽
*/
private void handleRotateDrag(float modelX, float modelY) {
if (selectedMesh == null) return;
ModelPart selectedPart = findPartByMesh(selectedMesh);
if (selectedPart == null) return;
// 计算当前角度
float currentAngle = (float) Math.atan2(modelY - rotationCenter.y,
modelX - rotationCenter.x);
// 计算旋转增量
float deltaAngle = currentAngle - rotationStartAngle;
// 应用旋转(基于初始旋转加上增量)
float newRotation = partInitialRotation + deltaAngle;
// 如果按住Shift键以15度为步长进行约束旋转
if (shiftPressed || shiftDuringDrag) {
float constraintStep = (float) (Math.PI / 12); // 15度
newRotation = Math.round(newRotation / constraintStep) * constraintStep;
}
selectedPart.setRotation(newRotation);
logger.debug("旋转角度: {} 度", Math.toDegrees(newRotation));
}
/**
* 处理移动中心点拖拽
*/
private void handleMovePivotDrag(float modelX, float modelY) {
if (selectedMesh == null) return;
ModelPart selectedPart = findPartByMesh(selectedMesh);
if (selectedPart == null) return;
float deltaX = modelX - dragStartX;
float deltaY = modelY - dragStartY;
Vector2f currentPivot = selectedPart.getPivot();
float newPivotX = currentPivot.x + deltaX;
float newPivotY = currentPivot.y + deltaY;
selectedPart.setPivot(newPivotX, newPivotY);
rotationCenter.set(newPivotX, newPivotY);
dragStartX = modelX;
dragStartY = modelY;
}
/**
* 处理移动拖拽
*/

View File

@@ -645,7 +645,6 @@ public class ModelPart {
// 保存拷贝的原始(局部)顶点供后续重算 world 顶点使用
float[] originalVertices = m.getVertices().clone();
m.setOriginalVertices(originalVertices);
logger.info("addMesh: texture={} for mesh={}", m.getTexture(), m.getName());
// 保证 UV 不被篡改(通常 copy() 已经处理)
// float[] uvs = m.getUVs(); // 如果需要可以在此处检查
@@ -663,16 +662,11 @@ public class ModelPart {
// 确保 GPU 数据在下一次绘制时会被上传(如果当前在渲染线程,也可以直接 uploadToGPU
m.markDirty();
// 如果你确定此处正在 GL 渲染线程并且想要立刻创建 VAO/VBO可取消下面注释
// m.uploadToGPU();
// 将拷贝加入到本部件
meshes.add(m);
boundsDirty = true;
}
/**
* 设置中心点
*/
@@ -686,17 +680,11 @@ public class ModelPart {
}
pivotInitialized = true;
}
float dx = x - pivot.x;
float dy = y - pivot.y;
pivot.set(x, y);
for (Mesh2D mesh : meshes) {
for (int i = 0; i < mesh.getVertexCount(); i++) {
Vector2f v = mesh.getVertex(i);
v.sub(dx, dy);
mesh.setVertex(i, v.x, v.y);
}
mesh.setPivot(x, y);
}
markTransformDirty();

View File

@@ -50,6 +50,7 @@ public class Mesh2D {
private boolean boundsDirty = true;
private boolean bakedToWorld = false;
private volatile boolean selected = false;
private Vector2f pivot = new Vector2f(0, 0);
// ==================== 常量 ====================
public static final int POINTS = 0;
@@ -59,6 +60,7 @@ public class Mesh2D {
public static final int TRIANGLE_STRIP = 4;
public static final int TRIANGLE_FAN = 5;
private static final float ROTATION_HANDLE_DISTANCE = 30.0f;
// ==================== 构造器 ====================
public Mesh2D() {
@@ -102,6 +104,31 @@ public class Mesh2D {
markDirty();
}
/**
* 设置中心点
*/
public void setPivot(float x, float y) {
this.pivot.set(x, y);
}
public void setPivot(Vector2f pivot) {
this.pivot.set(pivot);
}
/**
* 获取中心点
*/
public Vector2f getPivot() {
return new Vector2f(pivot);
}
/**
* 移动中心点
*/
public void movePivot(float dx, float dy) {
this.pivot.add(dx, dy);
}
/**
* 创建矩形网格
*/
@@ -576,6 +603,7 @@ public class Mesh2D {
float expand = 4.0f * 2.0f;
// 第1层外发光边框
bb.begin(RenderSystem.GL_LINE_LOOP, 4);
bb.setColor(new Vector4f(0.0f, 1.0f, 1.0f, 0.4f));
@@ -587,7 +615,7 @@ public class Mesh2D {
// 第2层主边框实心粗边框- 使用明亮的青色
bb.begin(RenderSystem.GL_LINE_LOOP, 4);
bb.setColor(new Vector4f(0.0f, 1.0f, 1.0f, 1.0f)); // 青色100%不透明
bb.setColor(new Vector4f(0.0f, 1.0f, 1.0f, 1.0f));
float mainExpand = 1.0f;
bb.vertex(minX - mainExpand, minY - mainExpand, 0.0f, 0.0f);
@@ -598,7 +626,7 @@ public class Mesh2D {
// 第3层内边框 - 使用白色增加对比度
bb.begin(RenderSystem.GL_LINE_LOOP, 4);
bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); // 白色100%不透明
bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 1.0f));
bb.vertex(minX, minY, 0.0f, 0.0f);
bb.vertex(maxX, minY, 0.0f, 0.0f);
@@ -608,6 +636,97 @@ public class Mesh2D {
// 第4层绘制角点标记和边线
drawResizeHandles(bb, minX, minY, maxX, maxY, CORNER_SIZE, BORDER_THICKNESS);
// 新增:绘制中心点
drawCenterPoint(bb, minX, minY, maxX, maxY);
drawRotationHandle(bb, minX, minY, maxX, maxY);
}
private void drawCenterPoint(BufferBuilder bb, float minX, float minY, float maxX, float maxY) {
// 使用 Mesh2D 的 pivot 作为中心点位置
float centerX = pivot.x;
float centerY = pivot.y;
float pointSize = 6.0f; // 中心点大小
Vector4f centerColor = new Vector4f(1.0f, 0.0f, 0.0f, 1.0f); // 红色中心点
// 绘制中心点(十字形)
bb.begin(GL11.GL_LINES, 4); // 使用 RenderSystem 常量
bb.setColor(centerColor);
// 水平线
bb.vertex(centerX - pointSize, centerY, 0.0f, 0.0f);
bb.vertex(centerX + pointSize, centerY, 0.0f, 0.0f);
// 垂直线
bb.vertex(centerX, centerY - pointSize, 0.0f, 0.0f);
bb.vertex(centerX, centerY + pointSize, 0.0f, 0.0f);
bb.endImmediate();
// 绘制中心点圆圈
bb.begin(RenderSystem.GL_LINE_LOOP, 12);
bb.setColor(centerColor);
float radius = pointSize * 0.8f;
for (int i = 0; i < 12; i++) {
float angle = (float) (i * 2 * Math.PI / 12);
float x = centerX + (float) Math.cos(angle) * radius;
float y = centerY + (float) Math.sin(angle) * radius;
bb.vertex(x, y, 0.0f, 0.0f);
}
bb.endImmediate();
logger.trace("绘制中心点: ({}, {})", centerX, centerY);
}
/**
* 绘制旋转手柄
*/
private void drawRotationHandle(BufferBuilder bb, float minX, float minY, float maxX, float maxY) {
// 使用 Mesh2D 的 pivot 作为中心点位置
float centerX = pivot.x;
float centerY = pivot.y;
// 旋转手柄位置(在边界框上方)
float rotationHandleY = minY - ROTATION_HANDLE_DISTANCE;
float rotationHandleX = centerX;
Vector4f rotationColor = new Vector4f(0.0f, 1.0f, 0.0f, 1.0f); // 绿色旋转手柄
// 绘制连接线(从中心点到旋转手柄)
bb.begin(GL11.GL_LINES, 2);
bb.setColor(rotationColor);
bb.vertex(centerX, minY, 0.0f, 0.0f);
bb.vertex(rotationHandleX, rotationHandleY, 0.0f, 0.0f);
bb.endImmediate();
// 绘制旋转手柄(圆圈)
float handleRadius = 6.0f;
bb.begin(RenderSystem.GL_LINE_LOOP, 12);
bb.setColor(rotationColor);
for (int i = 0; i < 12; i++) {
float angle = (float) (i * 2 * Math.PI / 12);
float x = rotationHandleX + (float) Math.cos(angle) * handleRadius;
float y = rotationHandleY + (float) Math.sin(angle) * handleRadius;
bb.vertex(x, y, 0.0f, 0.0f);
}
bb.endImmediate();
// 绘制旋转箭头
bb.begin(GL11.GL_LINES, 4);
bb.setColor(rotationColor);
// 箭头线
float arrowSize = 4.0f;
bb.vertex(rotationHandleX - arrowSize, rotationHandleY - arrowSize, 0.0f, 0.0f);
bb.vertex(rotationHandleX + arrowSize, rotationHandleY + arrowSize, 0.0f, 0.0f);
bb.vertex(rotationHandleX + arrowSize, rotationHandleY - arrowSize, 0.0f, 0.0f);
bb.vertex(rotationHandleX - arrowSize, rotationHandleY + arrowSize, 0.0f, 0.0f);
bb.endImmediate();
}
/**
@@ -807,38 +926,41 @@ public class Mesh2D {
/**
* 创建网格的深拷贝
*/
public Mesh2D copy() {
Mesh2D copy = new Mesh2D(name + "_copy");
public Mesh2D copy() {
Mesh2D copy = new Mesh2D(name + "_copy");
// 深拷贝数组(保证互不影响)
copy.vertices = this.vertices != null ? this.vertices.clone() : new float[0];
copy.uvs = this.uvs != null ? this.uvs.clone() : new float[0];
copy.indices = this.indices != null ? this.indices.clone() : new int[0];
// 深拷贝数组(保证互不影响)
copy.vertices = this.vertices != null ? this.vertices.clone() : new float[0];
copy.uvs = this.uvs != null ? this.uvs.clone() : new float[0];
copy.indices = this.indices != null ? this.indices.clone() : new int[0];
// 保留 originalVertices如果有否则把当前 vertices 作为原始数据
copy.originalVertices = this.originalVertices != null ? this.originalVertices.clone() : copy.vertices.clone();
// 保留 originalVertices如果有否则把当前 vertices 作为原始数据
copy.originalVertices = this.originalVertices != null ? this.originalVertices.clone() : copy.vertices.clone();
// 复制渲染/状态字段(保留纹理引用,但重置 GPU 句柄)
copy.texture = this.texture;
copy.visible = this.visible;
copy.drawMode = this.drawMode;
copy.bakedToWorld = this.bakedToWorld;
// 复制中心点
copy.pivot = new Vector2f(this.pivot);
// 重置 GPU 相关句柄,强制重新 uploadToGPU() 在渲染线程执行
copy.vaoId = -1;
copy.vboId = -1;
copy.eboId = -1;
copy.indexCount = this.indices != null ? this.indices.length : 0;
copy.uploaded = false;
// 复制渲染/状态字段(保留纹理引用,但重置 GPU 句柄)
copy.texture = this.texture;
copy.visible = this.visible;
copy.drawMode = this.drawMode;
copy.bakedToWorld = this.bakedToWorld;
// 状态标记
copy.dirty = true;
copy.boundsDirty = true;
copy.bounds = new BoundingBox();
copy.selected = this.selected;
// 重置 GPU 相关句柄,强制重新 uploadToGPU() 在渲染线程执行
copy.vaoId = -1;
copy.vboId = -1;
copy.eboId = -1;
copy.indexCount = this.indices != null ? this.indices.length : 0;
copy.uploaded = false;
return copy;
}
// 状态标记
copy.dirty = true;
copy.boundsDirty = true;
copy.bounds = new BoundingBox();
copy.selected = this.selected;
return copy;
}
public int getVaoId() {
@@ -881,7 +1003,8 @@ public class Mesh2D {
Objects.equals(name, mesh2D.name) &&
Objects.deepEquals(vertices, mesh2D.vertices) &&
Objects.deepEquals(uvs, mesh2D.uvs) &&
Objects.deepEquals(indices, mesh2D.indices);
Objects.deepEquals(indices, mesh2D.indices) &&
Objects.equals(pivot, mesh2D.pivot);
}
@Override
@@ -890,6 +1013,7 @@ public class Mesh2D {
java.util.Arrays.hashCode(vertices),
java.util.Arrays.hashCode(uvs),
java.util.Arrays.hashCode(indices),
pivot,
visible, drawMode);
}
@@ -900,6 +1024,8 @@ public class Mesh2D {
.append("name='").append(name).append('\'')
.append(", vertices=").append(getVertexCount())
.append(", indices=").append(indices.length)
.append(", pivot=(").append(String.format("%.2f", pivot.x))
.append(", ").append(String.format("%.2f", pivot.y)).append(")") // 新增这行
.append(", visible=").append(visible)
.append(", drawMode=").append(getDrawModeString())
.append(", bounds=").append(getBounds());
@@ -920,5 +1046,4 @@ public class Mesh2D {
sb.append('}');
return sb.toString();
}
}