feat(render): 实现 liquify overlay 显示控制与顶点同步优化

- 在 Mesh2D 中新增 isShowLiquifyOverlay 方法,用于控制 liquify overlay 的显示状态
- 修改 drawLiquifyOverlay 方法,增加对 mesh2D.isShowLiquifyOverlay() 的判断
- 重构 ModelPart 的 setPosition 方法,优化多选和单选状态下的顶点同步逻辑- 新增 syncSecondaryVerticesForPart 方法,实现部件及其子部件的二级顶点同步移动
- 移除 SelectionTool 中冗余的 syncSecondaryVerticesForPart 方法- 优化 SelectionTool 的 resize 操作逻辑,提高代码可读性和性能
- 在 VertexDeformationRander 中增加对 showSecondaryVertices 状态的检查- 完善多选操作时的中心点计算逻辑,提升用户体验
This commit is contained in:
tzdwindows 7
2025-11-01 19:17:03 +08:00
parent 5c66838b3e
commit c5097f91be
5 changed files with 97 additions and 84 deletions

View File

@@ -324,9 +324,6 @@ public class SelectionTool extends Tool {
for (ModelPart part : selectedParts) {
Vector2f pos = part.getPosition();
part.setPosition(pos.x + deltaX, pos.y + deltaY);
// 同步移动该部件下的所有网格的二级顶点
syncSecondaryVerticesForPart(part, deltaX, deltaY);
}
// 更新拖拽起始位置
@@ -395,16 +392,12 @@ public class SelectionTool extends Tool {
private void handleResizeDrag(float modelX, float modelY) {
if (lastSelectedMesh == null) return;
ModelPart selectedPart = findPartByMesh(lastSelectedMesh);
if (selectedPart == null) return;
float deltaX = modelX - dragStartX;
float deltaY = modelY - dragStartY;
float relScaleX = 1.0f;
float relScaleY = 1.0f;
// 根据拖拽模式计算相对缩放比例
switch (currentDragMode) {
case RESIZE_LEFT:
relScaleX = (resizeStartWidth - deltaX) / Math.max(1e-6f, resizeStartWidth);
@@ -436,92 +429,47 @@ public class SelectionTool extends Tool {
break;
}
// 如果按住Shift键等比例缩放
// Shift 键等比例缩放
if (renderPanel.getKeyboardManager().getIsShiftPressed() || shiftDuringDrag) {
float uniform = (relScaleX + relScaleY) * 0.5f;
relScaleX = uniform;
relScaleY = uniform;
}
// 应用缩放到所有选中的部件
List<ModelPart> selectedParts = getSelectedParts();
Vector2f center = getMultiSelectionCenter(); // 整个多选的中心点
for (ModelPart part : selectedParts) {
Vector2f currentScale = part.getScale();
part.setScale(currentScale.x * relScaleX, currentScale.y * relScaleY);
float newScaleX = currentScale.x * relScaleX;
float newScaleY = currentScale.y * relScaleY;
// 同步缩放该部件下的所有网格的二级顶点
//syncSecondaryVerticesScaleForPart(part, relScaleX, relScaleY);
// 更新部件自身缩放
part.setScale(newScaleX, newScaleY);
}
// 更新拖拽起始位置和初始尺寸
// 更新拖拽起始点和尺寸
dragStartX = modelX;
dragStartY = modelY;
resizeStartWidth *= relScaleX;
resizeStartHeight *= relScaleY;
}
/**
* 同步部件下所有网格的二级顶点位置
*/
private void syncSecondaryVerticesForPart(ModelPart part, float deltaX, float deltaY) {
if (part == null) return;
private Vector2f getMultiSelectionCenter() {
List<ModelPart> selectedParts = getSelectedParts();
if (selectedParts.isEmpty()) return new Vector2f(0, 0);
List<Mesh2D> meshes = part.getMeshes();
if (meshes == null) return;
float sumX = 0f;
float sumY = 0f;
for (Mesh2D mesh : meshes) {
if (mesh != null && mesh.isVisible() && mesh.getSecondaryVertexCount() > 0) {
List<SecondaryVertex> secondaryVertices = mesh.getSecondaryVertices();
if (secondaryVertices != null) {
// 遍历所有顶点,逐个调用 moveSecondaryVertex
for (SecondaryVertex vertex : secondaryVertices) {
// 【修正 1避免双重平移和状态冲突】
// 仅对未锁定/未固定的顶点执行局部坐标平移。
// 锁定的顶点不应被工具的同步逻辑移动,它们应该随 ModelPart 的世界变换移动。
if (!vertex.isLocked() && !vertex.isPinned()) {
// 计算顶点的新局部坐标 (position + delta)
float newX = vertex.getPosition().x + deltaX;
float newY = vertex.getPosition().y + deltaY;
// 使用 moveSecondaryVertex 方法
mesh.moveSecondaryVertex(vertex, newX, newY);
// 注意mesh.moveSecondaryVertex 内部会触发形变计算和 markDirty
}
}
}
}
}
part.setPosition(part.getPosition());
// 递归处理子部件
for (ModelPart child : part.getChildren()) {
syncSecondaryVerticesForPart(child, deltaX, deltaY);
}
}
/**
* 同步部件下所有网格的二级顶点缩放
*/
private void syncSecondaryVerticesScaleForPart(ModelPart part, float scaleX, float scaleY) {
if (part == null) return;
List<Mesh2D> meshes = part.getMeshes();
if (meshes == null) return;
for (Mesh2D mesh : meshes) {
if (mesh != null && mesh.getSecondaryVertexCount() > 0) {
mesh.syncSecondaryVerticesToBounds();
}
for (ModelPart part : selectedParts) {
Vector2f pos = part.getPosition();
sumX += pos.x;
sumY += pos.y;
}
// 递归处理子部件
for (ModelPart child : part.getChildren()) {
syncSecondaryVerticesScaleForPart(child, scaleX, scaleY);
}
return new Vector2f(sumX / selectedParts.size(), sumY / selectedParts.size());
}
/**

View File

@@ -1549,7 +1549,9 @@ public class ModelPart {
public void setPosition(float x, float y) {
// 防止递归调用
if (inMultiSelectionOperation) {
// 直接执行单选择辑,避免递归
float deltaX = x - position.x;
float deltaY = y - position.y;
position.set(x, y);
markTransformDirty();
updateLocalTransform();
@@ -1560,28 +1562,42 @@ public class ModelPart {
mesh.setPivot(worldPivot.x, worldPivot.y);
}
updateMeshVertices();
syncSecondaryVerticesForPart(this, deltaX, deltaY);
triggerEvent("position");
return;
}
// 如果是多选状态下的移动,使用多选移动方法
if (isInMultiSelection() && !getSelectedMeshes().isEmpty()) {
Vector2f currentPos = getPosition();
float dx = x - currentPos.x;
float dy = y - currentPos.y;
float oldX = position.x;
float oldY = position.y;
// 多选状态下移动
if (isInMultiSelection() && !getSelectedMeshes().isEmpty()) {
float dx = x - oldX;
float dy = y - oldY;
// 设置标志防止递归
inMultiSelectionOperation = true;
try {
moveSelectedMeshes(dx, dy);
moveSelectedMeshes(dx, dy); // 这里会调用 setPosition但被 inMultiSelectionOperation 拦截
} finally {
inMultiSelectionOperation = false;
}
// 更新自身位置
position.set(x, y);
markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
for (Mesh2D mesh : meshes) {
Vector2f worldPivot = Matrix3fUtils.transformPoint(worldTransform, mesh.getOriginalPivot());
mesh.setPivot(worldPivot.x, worldPivot.y);
}
triggerEvent("position");
return;
}
// 原有单选
// 单选
position.set(x, y);
markTransformDirty();
updateLocalTransform();
@@ -1592,12 +1608,57 @@ public class ModelPart {
mesh.setPivot(worldPivot.x, worldPivot.y);
}
float deltaX = x - oldX;
float deltaY = y - oldY;
updateMeshVertices();
syncSecondaryVerticesForPart(this, deltaX, deltaY);
triggerEvent("position");
}
/**
* 同步部件下所有网格的二级顶点位置
*/
private void syncSecondaryVerticesForPart(ModelPart part, float deltaX, float deltaY) {
if (part == null) return;
List<Mesh2D> meshes = part.getMeshes();
if (meshes == null) return;
for (Mesh2D mesh : meshes) {
if (mesh != null && mesh.isVisible() && mesh.getSecondaryVertexCount() > 0) {
List<SecondaryVertex> secondaryVertices = mesh.getSecondaryVertices();
if (secondaryVertices != null) {
// 遍历所有顶点,逐个调用 moveSecondaryVertex
for (SecondaryVertex vertex : secondaryVertices) {
// 【修正 1避免双重平移和状态冲突】
// 仅对未锁定/未固定的顶点执行局部坐标平移。
// 锁定的顶点不应被工具的同步逻辑移动,它们应该随 ModelPart 的世界变换移动。
if (!vertex.isLocked() && !vertex.isPinned()) {
// 计算顶点的新局部坐标 (position + delta)
float newX = vertex.getPosition().x + deltaX;
float newY = vertex.getPosition().y + deltaY;
// 使用 moveSecondaryVertex 方法
mesh.moveSecondaryVertex(vertex, newX, newY);
// 注意mesh.moveSecondaryVertex 内部会触发形变计算和 markDirty
}
}
}
}
}
part.setPosition(part.getPosition());
// 递归处理子部件
for (ModelPart child : part.getChildren()) {
syncSecondaryVerticesForPart(child, deltaX, deltaY);
}
}
/**
* 更新所有网格的顶点位置以反映当前变换
*/

View File

@@ -657,6 +657,10 @@ public class Mesh2D {
markDirty();
}
public boolean isShowLiquifyOverlay() {
return showLiquifyOverlay;
}
/**
* 设置中心点
*/

View File

@@ -53,7 +53,7 @@ public class LiquifyTargetPartRander extends RanderTools {
* 主渲染入口。收集文本命令到 pendingTexts绘制完成后 popState再把文本绘制出来外部渲染
*/
private void drawLiquifyOverlay(Mesh2D mesh2D, Matrix3f modelMatrix) {
if (!isAlgorithmEnabled("showLiquifyOverlay")) return;
if (!isAlgorithmEnabled("showLiquifyOverlay") || !mesh2D.isShowLiquifyOverlay()) return;
List<TextItem> pendingTexts = new ArrayList<>();

View File

@@ -41,7 +41,7 @@ public class VertexDeformationRander extends RanderTools {
* 绘制二级顶点(现代化样式)
*/
private void drawSecondaryVertices(Mesh2D mesh2D, Matrix3f modelMatrix) {
if (!isAlgorithmEnabled("showSecondaryVertices") || mesh2D.getSecondaryVertices().isEmpty()) return;
if (!isAlgorithmEnabled("showSecondaryVertices") || mesh2D.getSecondaryVertices().isEmpty() || !mesh2D.isShowSecondaryVertices()) return;
RenderSystem.pushState();
try {