refactor(render): 优化顶点更新与选框渲染逻辑

- 调整 Mesh2D 中选中框绘制的状态压栈顺序,确保渲染状态正确恢复
- 在 ModelPart 更新顶点位置后触发 vertex 事件,携带新旧顶点信息
- 简化 MultiSelectionBoxRenderer 的边框绘制逻辑,移除多层边框结构
- 使用 GL_LINES 模式重写选框边界绘制,提升性能与清晰度
- 修改 ParametersManagement 监听 vertex 事件并实现顶点关键帧更新逻辑
- 为 broadcast 方法增加指定关键帧参数重载,增强灵活性
- 移除 SelectionTool 中冗余的网格顶点更新方法体,待后续重构
This commit is contained in:
tzdwindows 7
2025-11-21 16:12:13 +08:00
parent 41c3afecc8
commit 0d2bb8d6f7
5 changed files with 154 additions and 87 deletions

View File

@@ -25,14 +25,9 @@ public class MultiSelectionBoxRenderer {
// -------------------- 配置常量(可调) -------------------- // -------------------- 配置常量(可调) --------------------
// 尺寸 // 尺寸
public static final float DEFAULT_CORNER_SIZE = 8.0f; 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_DASH_LENGTH = 10.0f;
public static final float DEFAULT_GAP_LENGTH = 6.0f; public static final float DEFAULT_GAP_LENGTH = 6.0f;
private static final float MAIN_BORDER_THICKNESS = 2.5f;
// 视觉厚度分层(更细腻)
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_CORNER_SIZE = DEFAULT_CORNER_SIZE;
@@ -60,56 +55,47 @@ public class MultiSelectionBoxRenderer {
*/ */
public static void drawSelectBox(BoundingBox bounds, Vector2f pivot) { public static void drawSelectBox(BoundingBox bounds, Vector2f pivot) {
if (bounds == null || !bounds.isValid()) return; if (bounds == null || !bounds.isValid()) return;
float minX = bounds.getMinX(); float minX = bounds.getMinX();
float minY = bounds.getMinY(); float minY = bounds.getMinY();
float maxX = bounds.getMaxX(); float maxX = bounds.getMaxX();
float maxY = bounds.getMaxY(); float maxY = bounds.getMaxY();
Tesselator tesselator = Tesselator.getInstance(); Tesselator tesselator = Tesselator.getInstance();
BufferBuilder bb = tesselator.getBuilder(); BufferBuilder bb = tesselator.getBuilder();
RenderSystem.enableBlend(); RenderSystem.enableBlend();
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
float scaledCornerSize = Math.max(4.0f, DEFAULT_CORNER_SIZE);
// 估算顶点数:三层边框 + 8 个手柄 + 中心点十字和圆环 float scaledMidSize = Math.max(2.0f, HANDLE_MID_SIZE);
// 每个四边形使用 6 个顶点(两个三角形) bb.begin(GL11.GL_LINES, 8); // 4条线 * 2个顶点 = 8个顶点
int estimatedQuads = (3 * 4) + 8 + (6 * 4); // 近似估算(保守)
bb.begin(RenderSystem.GL_TRIANGLES, estimatedQuads * 6);
// 外层发光边框(较宽、透明)
bb.setColor(SOLID_BORDER_COLOR_OUTER);
addQuadLineLoop(bb, OUTER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
// 主边框(核心线)
bb.setColor(SOLID_BORDER_COLOR_MAIN); bb.setColor(SOLID_BORDER_COLOR_MAIN);
addQuadLineLoop(bb, MAIN_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY); addLineVertices(bb, minX, minY, maxX, minY); // 上边
addLineVertices(bb, maxX, minY, maxX, maxY); // 右边
// 内描边(细) addLineVertices(bb, maxX, maxY, minX, maxY); // 下边
bb.setColor(SOLID_BORDER_COLOR_INNER); addLineVertices(bb, minX, maxY, minX, minY); // 左边
addQuadLineLoop(bb, INNER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY); tesselator.end();
bb.begin(RenderSystem.GL_TRIANGLES, (8 + 2) * 6);
// 手柄(角点与边中点)
bb.setColor(HANDLE_COLOR); bb.setColor(HANDLE_COLOR);
addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE); addHandleQuad(bb, minX, minY, scaledCornerSize);
addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE); addHandleQuad(bb, maxX, minY, scaledCornerSize);
addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE); addHandleQuad(bb, minX, maxY, scaledCornerSize);
addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE); addHandleQuad(bb, maxX, maxY, scaledCornerSize);
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, scaledMidSize);
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, HANDLE_MID_SIZE); addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, scaledMidSize);
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, HANDLE_MID_SIZE); addHandleQuad(bb, minX, (minY + maxY) * 0.5f, scaledMidSize);
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE); addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, scaledMidSize);
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
// 中心点:十字 + 环(圆环使用三角片段)
bb.setColor(CENTER_POINT_COLOR); bb.setColor(CENTER_POINT_COLOR);
addQuadLine(bb, pivot.x - 6.0f, pivot.y, pivot.x + 6.0f, pivot.y, CENTER_LINE_THICKNESS); 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); 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(); tesselator.end();
} }
/**
* 添加简单的线段顶点GL_LINES模式
*/
private static void addLineVertices(BufferBuilder bb, float x0, float y0, float x1, float y1) {
bb.vertex(x0, y0, 0.0f, 0.0f);
bb.vertex(x1, y1, 0.0f, 0.0f);
}
/** /**
* 绘制多选框(虚线 + 手柄) * 绘制多选框(虚线 + 手柄)
* *
@@ -202,16 +188,16 @@ public class MultiSelectionBoxRenderer {
float v2x = x1 - nx; float v2y = y1 - ny; float v2x = x1 - nx; float v2y = y1 - ny;
float v3x = x0 - nx; float v3y = y0 - ny; float v3x = x0 - nx; float v3y = y0 - ny;
// tri1
bb.vertex(v3x, v3y, 0.0f, 0.0f);
bb.vertex(v0x, v0y, 0.0f, 0.0f); bb.vertex(v0x, v0y, 0.0f, 0.0f);
bb.vertex(v1x, v1y, 0.0f, 0.0f); bb.vertex(v1x, v1y, 0.0f, 0.0f);
// tri2 bb.vertex(v2x, v2y, 0.0f, 0.0f);
bb.vertex(v1x, v1y, 0.0f, 0.0f);
bb.vertex(v2x, v2y, 0.0f, 0.0f); bb.vertex(v2x, v2y, 0.0f, 0.0f);
bb.vertex(v3x, v3y, 0.0f, 0.0f); bb.vertex(v3x, v3y, 0.0f, 0.0f);
bb.vertex(v0x, v0y, 0.0f, 0.0f);
} }
/** /**
* 绘制一个闭合的四边形线环(用于边框三层绘制) * 绘制一个闭合的四边形线环(用于边框三层绘制)
* *
@@ -219,7 +205,7 @@ public class MultiSelectionBoxRenderer {
* @param vertices 顶点序列 x1,y1,x2,y2,... * @param vertices 顶点序列 x1,y1,x2,y2,...
*/ */
private static void addQuadLineLoop(BufferBuilder bb, float thickness, float... vertices) { private static void addQuadLineLoop(BufferBuilder bb, float thickness, float... vertices) {
if (vertices == null || vertices.length < 4) return; if (vertices == null || vertices.length < 8) return;
int n = vertices.length / 2; int n = vertices.length / 2;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
float x0 = vertices[(i * 2)]; float x0 = vertices[(i * 2)];

View File

@@ -1,10 +1,13 @@
package com.chuangzhou.vivid2D.render.awt.manager; package com.chuangzhou.vivid2D.render.awt.manager;
import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel;
import com.chuangzhou.vivid2D.render.awt.ParametersPanel; import com.chuangzhou.vivid2D.render.awt.ParametersPanel;
import com.chuangzhou.vivid2D.render.awt.manager.data.LayerOperationManagerData; import com.chuangzhou.vivid2D.render.awt.manager.data.LayerOperationManagerData;
import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData; import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData;
import com.chuangzhou.vivid2D.render.model.AnimationParameter; import com.chuangzhou.vivid2D.render.model.AnimationParameter;
import com.chuangzhou.vivid2D.render.model.ModelEvent;
import com.chuangzhou.vivid2D.render.model.ModelPart; import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.util.Vertex;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -16,12 +19,102 @@ import java.util.*;
public class ParametersManagement { public class ParametersManagement {
private static final Logger logger = LoggerFactory.getLogger(ParametersManagement.class); private static final Logger logger = LoggerFactory.getLogger(ParametersManagement.class);
private final ParametersPanel parametersPanel; private final ParametersPanel parametersPanel;
private final ModelRenderPanel renderPanel;
public List<Parameter> oldValues = new ArrayList<>(); public List<Parameter> oldValues = new ArrayList<>();
public ParametersManagement(ParametersPanel parametersPanel) { public ParametersManagement(ParametersPanel parametersPanel) {
this.parametersPanel = parametersPanel; this.parametersPanel = parametersPanel;
this.renderPanel = parametersPanel.getRenderPanel();
for (int i = 0; i < renderPanel.getModel().getParts().size(); i++) {
ModelPart modelPart = renderPanel.getModel().getParts().get(i);
modelPart.addEvent((eventName, eventBus) -> {
if (eventName.equals("vertex")){
if (!(eventBus instanceof Map)) {
System.err.println("Error: eventBus is not a Map for vertex event.");
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> eventPayload = (Map<String, Object>) eventBus;
ModelPart caller = (ModelPart) eventPayload.get("caller");
Vertex oldVertexObj = (Vertex) eventPayload.get("oldVertex");
Vertex newVertexObj = (Vertex) eventPayload.get("newVertex");
updateVertex(caller, oldVertexObj, newVertexObj);
}
});
}
} }
/**
* 查找并替换一个顶点的关键帧数据。
* 它会基于旧的顶点状态(oldVertexObj)在当前选定的关键帧上查找一个完全匹配的记录。
* - 如果找到,则用新顶点状态(newVertexObj)的数据替换它。
* - 如果没有找到,则调用 broadcast 创建一个新的关键帧记录。
*
* @param caller 触发事件的 ModelPart
* @param oldVertexObj 变化前的 Vertex 状态,用于查找要替换的目标
* @param newVertexObj 变化后的 Vertex 状态,用于提供新的数据
*/
public void updateVertex(ModelPart caller, Vertex oldVertexObj, Vertex newVertexObj) {
if (newVertexObj == null || newVertexObj.getName() == null || oldVertexObj == null || oldVertexObj.getName() == null) {
return;
}
boolean updatedExisting = false;
for (int i = 0; i < oldValues.size(); i++) {
Parameter existingParameter = oldValues.get(i);
if (existingParameter.modelPart().equals(caller)) {
List<Object> values = existingParameter.value();
for (int j = 0; j < values.size(); j++) {
if (!"meshVertices".equals(existingParameter.paramId().get(j))) {
continue;
}
Object value = values.get(j);
if (!(value instanceof Map)) {
continue;
}
@SuppressWarnings("unchecked")
Map<String, Object> payload = (Map<String, Object>) value;
String storedVertexId = (String) payload.get("id");
float[] storedPosition = (float[]) payload.get("Vertex");
if (!Objects.equals(storedVertexId, oldVertexObj.getName())) {
continue;
}
if (storedPosition == null || storedPosition.length != 2) {
continue;
}
final float epsilon = 1e-5f;
boolean positionMatches = Math.abs(storedPosition[0] - oldVertexObj.position.x) < epsilon &&
Math.abs(storedPosition[1] - oldVertexObj.position.y) < epsilon;
if (positionMatches) {
logger.debug("在{}关键帧中找到原来匹配的顶点ID:{})并执行原地更新。", existingParameter.keyframe().get(j), oldVertexObj.getName());
Map<String, Object> newVertexUpdatePayload = Map.of(
"id", newVertexObj.getName(),
"Vertex", new float[]{newVertexObj.position.x, newVertexObj.position.y}
);
List<Object> newValues = new ArrayList<>(values);
newValues.set(j, newVertexUpdatePayload);
Parameter updatedParameter = new Parameter(
existingParameter.modelPart(),
new ArrayList<>(existingParameter.animationParameter()),
new ArrayList<>(existingParameter.paramId()),
newValues,
new ArrayList<>(existingParameter.keyframe()),
new ArrayList<>(existingParameter.isKeyframe())
);
oldValues.set(i, updatedParameter);
updatedExisting = true;
// break;
}
}
}
if (updatedExisting) {
break;
}
}
}
public static ParametersManagement getInstance(ParametersPanel parametersPanel) { public static ParametersManagement getInstance(ParametersPanel parametersPanel) {
String managementFilePath = parametersPanel.getRenderPanel().getGlContextManager().getModelPath() + ".data"; String managementFilePath = parametersPanel.getRenderPanel().getGlContextManager().getModelPath() + ".data";
File managementFile = new File(managementFilePath); File managementFile = new File(managementFilePath);
@@ -137,16 +230,15 @@ public class ParametersManagement {
* @param paramId 参数id * @param paramId 参数id
* @param value 最终值 * @param value 最终值
*/ */
public void broadcast(ModelPart modelPart, String paramId, Object value) { public void broadcast(ModelPart modelPart, String paramId, Object value, Float specifiedKeyframe) {
if (getSelectParameter() == null) { if (getSelectParameter() == null) {
return; return;
} }
AnimationParameter currentAnimParam = getSelectParameter(); AnimationParameter currentAnimParam = getSelectParameter();
Float currentKeyframe = getSelectedKeyframe(false); if (specifiedKeyframe == null) {
if (currentKeyframe == null) {
return; return;
} }
boolean isKeyframe = currentAnimParam.getKeyframes().contains(currentKeyframe); boolean isKeyframe = currentAnimParam.getKeyframes().contains(specifiedKeyframe);
String newId = null; String newId = null;
if (paramId.equals("meshVertices") && value instanceof Map) { if (paramId.equals("meshVertices") && value instanceof Map) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -164,13 +256,15 @@ public class ParametersManagement {
List<Object> newValues = new ArrayList<>(existingParameter.value()); List<Object> newValues = new ArrayList<>(existingParameter.value());
List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe()); List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe());
List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe()); List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe());
int existingIndex = -1; int existingIndex = -1;
for (int j = 0; j < newKeyframes.size(); j++) { for (int j = 0; j < newKeyframes.size(); j++) {
boolean keyframeMatches = Objects.equals(newKeyframes.get(j), currentKeyframe); boolean keyframeMatches = Objects.equals(newKeyframes.get(j), specifiedKeyframe);
boolean paramIdMatches = paramId.equals(newParamIds.get(j)); boolean paramIdMatches = paramId.equals(newParamIds.get(j));
AnimationParameter recordAnimParam = newAnimationParameters.get(j); AnimationParameter recordAnimParam = newAnimationParameters.get(j);
boolean animParamMatches = recordAnimParam != null && recordAnimParam.equals(currentAnimParam); boolean animParamMatches = recordAnimParam != null && recordAnimParam.equals(currentAnimParam);
boolean idMatches = true; boolean idMatches = true;
if (paramIdMatches && paramId.equals("meshVertices")) { if (paramIdMatches && paramId.equals("meshVertices")) {
Object oldValue = newValues.get(j); Object oldValue = newValues.get(j);
if (oldValue instanceof Map) { if (oldValue instanceof Map) {
@@ -183,36 +277,46 @@ public class ParametersManagement {
idMatches = false; idMatches = false;
} }
} }
if (keyframeMatches && paramIdMatches && animParamMatches && idMatches) { if (keyframeMatches && paramIdMatches && animParamMatches && idMatches) {
existingIndex = j; existingIndex = j;
break; break;
} }
} }
if (existingIndex != -1) { if (existingIndex != -1) {
newValues.set(existingIndex, value); newValues.set(existingIndex, value);
} else { } else {
newAnimationParameters.add(currentAnimParam); newAnimationParameters.add(currentAnimParam);
newParamIds.add(paramId); newParamIds.add(paramId);
newValues.add(value); newValues.add(value);
newKeyframes.add(currentKeyframe); newKeyframes.add(specifiedKeyframe);
newIsKeyframes.add(isKeyframe); newIsKeyframes.add(isKeyframe);
} }
Parameter updatedParameter = new Parameter(modelPart, newAnimationParameters, newParamIds, newValues, newKeyframes, newIsKeyframes); Parameter updatedParameter = new Parameter(modelPart, newAnimationParameters, newParamIds, newValues, newKeyframes, newIsKeyframes);
oldValues.set(i, updatedParameter); oldValues.set(i, updatedParameter);
return; return;
} }
} }
// 如果没有找到现有的参数记录,创建新的
Parameter parameter = new Parameter( Parameter parameter = new Parameter(
modelPart, modelPart,
Collections.singletonList(currentAnimParam), Collections.singletonList(currentAnimParam),
Collections.singletonList(paramId), Collections.singletonList(paramId),
Collections.singletonList(value), Collections.singletonList(value),
Collections.singletonList(currentKeyframe), Collections.singletonList(specifiedKeyframe),
Collections.singletonList(isKeyframe) Collections.singletonList(isKeyframe)
); );
oldValues.add(parameter); oldValues.add(parameter);
} }
public void broadcast(ModelPart modelPart, String paramId, Object value) {
Float currentKeyframe = getSelectedKeyframe(false);
broadcast(modelPart, paramId, value, currentKeyframe);
}
/** /**
* 移除特定参数 * 移除特定参数
* @param modelPart 部件 * @param modelPart 部件
@@ -321,7 +425,7 @@ public class ParametersManagement {
} }
sb.append(String.format("{ID=%s, V=%s, KF=%s, IsKF=%b}", sb.append(String.format("{ID=%s, V=%s, KF=%s, IsKF=%b}",
id, id,
String.valueOf(val), val,
kf != null ? String.valueOf(kf) : "null", kf != null ? String.valueOf(kf) : "null",
isKf)); isKf));
} }

View File

@@ -1254,38 +1254,8 @@ public class SelectionTool extends Tool {
updateMeshVertices(hoveredMesh); updateMeshVertices(hoveredMesh);
} }
@SuppressWarnings("unchecked")
public void updateMeshVertices(Mesh2D mesh) { public void updateMeshVertices(Mesh2D mesh) {
if (mesh == null){ //
return;
}
ParametersManagement.Parameter param = renderPanel.getParametersManagement().getValue(mesh.getModelPart(), "meshVertices");
if (param == null || param.value().isEmpty() || param.keyframe().isEmpty()) {
return;
}
List<Object> paramValue = param.value();
for (Object keyframeDataObject : paramValue) {
if (!(keyframeDataObject instanceof Map)) {
continue;
}
Map<String, Object> keyframeData = (Map<String, Object>) keyframeDataObject;
Object idObject = keyframeData.get("id");
if (!(idObject instanceof String vertexIdInMap)) {
continue;
}
for (Vertex vertex : mesh.getActiveVertexList()) {
if (vertex == null || vertex.getName() == null) {
continue;
}
if (vertexIdInMap.equals(vertex.getName())) {
Map<String, Object> vertexUpdatePayload = Map.of(
"id", vertex.getName(),
"Vertex", new float[]{vertex.position.x, vertex.position.y}
);
renderPanel.getParametersManagement().broadcast(mesh.getModelPart(), "meshVertices", vertexUpdatePayload);
}
}
}
} }
/** /**

View File

@@ -1542,13 +1542,11 @@ public class Mesh2D {
// 选中框绘制(需要切换到固色 shader // 选中框绘制(需要切换到固色 shader
if (selected && !isRenderVertices) { if (selected && !isRenderVertices) {
RenderSystem.pushState();
RenderSystem.enableBlend(); RenderSystem.enableBlend();
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.pushState();
try { try {
setSolidShader(modelMatrix); setSolidShader(modelMatrix);
if (isInMultiSelection()) { if (isInMultiSelection()) {
drawMultiSelectionBox(); drawMultiSelectionBox();
} else { } else {

View File

@@ -1608,9 +1608,18 @@ public class ModelPart {
for (Mesh2D mesh : meshes) { for (Mesh2D mesh : meshes) {
if (mesh == null) continue; if (mesh == null) continue;
for (Vertex vertex : mesh.getActiveVertexList()) { for (Vertex vertex : mesh.getActiveVertexList()) {
Vertex oldVertexCopy = vertex.copy();
Vector2f localPoint = vertex.originalPosition; Vector2f localPoint = vertex.originalPosition;
Vector2f worldPoint = Matrix3fUtils.transformPoint(this.worldTransform, localPoint); Vector2f worldPoint = Matrix3fUtils.transformPoint(this.worldTransform, localPoint);
vertex.position.set(worldPoint); vertex.position.set(worldPoint);
Map<String, Object> eventPayload = new HashMap<>();
eventPayload.put("caller", this);
eventPayload.put("oldVertex", oldVertexCopy);
eventPayload.put("newVertex", vertex);
for (ModelEvent event : events) {
if (event != null)
event.trigger("vertex", eventPayload);
}
} }
mesh.markDirty(); mesh.markDirty();
} }