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_BORDER_THICKNESS = 2.5f;
public static final float DEFAULT_DASH_LENGTH = 10.0f;
public static final float DEFAULT_GAP_LENGTH = 6.0f;
// 视觉厚度分层(更细腻)
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 MAIN_BORDER_THICKNESS = 2.5f;
// 手柄与中心点尺寸
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) {
if (bounds == null || !bounds.isValid()) return;
float minX = bounds.getMinX();
float minY = bounds.getMinY();
float maxX = bounds.getMaxX();
float maxY = bounds.getMaxY();
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder bb = tesselator.getBuilder();
RenderSystem.enableBlend();
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// 估算顶点数:三层边框 + 8 个手柄 + 中心点十字和圆环
// 每个四边形使用 6 个顶点(两个三角形)
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);
// 主边框(核心线)
float scaledCornerSize = Math.max(4.0f, DEFAULT_CORNER_SIZE);
float scaledMidSize = Math.max(2.0f, HANDLE_MID_SIZE);
bb.begin(GL11.GL_LINES, 8); // 4条线 * 2个顶点 = 8个顶点
bb.setColor(SOLID_BORDER_COLOR_MAIN);
addQuadLineLoop(bb, MAIN_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
// 内描边(细)
bb.setColor(SOLID_BORDER_COLOR_INNER);
addQuadLineLoop(bb, INNER_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); // 下边
addLineVertices(bb, minX, maxY, minX, minY); // 左边
tesselator.end();
bb.begin(RenderSystem.GL_TRIANGLES, (8 + 2) * 6);
bb.setColor(HANDLE_COLOR);
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, 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);
// 中心点:十字 + 环(圆环使用三角片段)
addHandleQuad(bb, minX, minY, scaledCornerSize);
addHandleQuad(bb, maxX, minY, scaledCornerSize);
addHandleQuad(bb, minX, maxY, scaledCornerSize);
addHandleQuad(bb, maxX, maxY, scaledCornerSize);
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, scaledMidSize);
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, scaledMidSize);
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, scaledMidSize);
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, scaledMidSize);
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, 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();
}
/**
* 添加简单的线段顶点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 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(v1x, v1y, 0.0f, 0.0f);
// tri2
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(v0x, v0y, 0.0f, 0.0f);
}
/**
* 绘制一个闭合的四边形线环(用于边框三层绘制)
*
@@ -219,7 +205,7 @@ public class MultiSelectionBoxRenderer {
* @param vertices 顶点序列 x1,y1,x2,y2,...
*/
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;
for (int i = 0; i < n; i++) {
float x0 = vertices[(i * 2)];

View File

@@ -1,10 +1,13 @@
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.manager.data.LayerOperationManagerData;
import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData;
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.util.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,12 +19,102 @@ import java.util.*;
public class ParametersManagement {
private static final Logger logger = LoggerFactory.getLogger(ParametersManagement.class);
private final ParametersPanel parametersPanel;
private final ModelRenderPanel renderPanel;
public List<Parameter> oldValues = new ArrayList<>();
public ParametersManagement(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) {
String managementFilePath = parametersPanel.getRenderPanel().getGlContextManager().getModelPath() + ".data";
File managementFile = new File(managementFilePath);
@@ -137,16 +230,15 @@ public class ParametersManagement {
* @param paramId 参数id
* @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) {
return;
}
AnimationParameter currentAnimParam = getSelectParameter();
Float currentKeyframe = getSelectedKeyframe(false);
if (currentKeyframe == null) {
if (specifiedKeyframe == null) {
return;
}
boolean isKeyframe = currentAnimParam.getKeyframes().contains(currentKeyframe);
boolean isKeyframe = currentAnimParam.getKeyframes().contains(specifiedKeyframe);
String newId = null;
if (paramId.equals("meshVertices") && value instanceof Map) {
@SuppressWarnings("unchecked")
@@ -164,13 +256,15 @@ public class ParametersManagement {
List<Object> newValues = new ArrayList<>(existingParameter.value());
List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe());
List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe());
int existingIndex = -1;
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));
AnimationParameter recordAnimParam = newAnimationParameters.get(j);
boolean animParamMatches = recordAnimParam != null && recordAnimParam.equals(currentAnimParam);
boolean idMatches = true;
if (paramIdMatches && paramId.equals("meshVertices")) {
Object oldValue = newValues.get(j);
if (oldValue instanceof Map) {
@@ -183,36 +277,46 @@ public class ParametersManagement {
idMatches = false;
}
}
if (keyframeMatches && paramIdMatches && animParamMatches && idMatches) {
existingIndex = j;
break;
}
}
if (existingIndex != -1) {
newValues.set(existingIndex, value);
} else {
newAnimationParameters.add(currentAnimParam);
newParamIds.add(paramId);
newValues.add(value);
newKeyframes.add(currentKeyframe);
newKeyframes.add(specifiedKeyframe);
newIsKeyframes.add(isKeyframe);
}
Parameter updatedParameter = new Parameter(modelPart, newAnimationParameters, newParamIds, newValues, newKeyframes, newIsKeyframes);
oldValues.set(i, updatedParameter);
return;
}
}
// 如果没有找到现有的参数记录,创建新的
Parameter parameter = new Parameter(
modelPart,
Collections.singletonList(currentAnimParam),
Collections.singletonList(paramId),
Collections.singletonList(value),
Collections.singletonList(currentKeyframe),
Collections.singletonList(specifiedKeyframe),
Collections.singletonList(isKeyframe)
);
oldValues.add(parameter);
}
public void broadcast(ModelPart modelPart, String paramId, Object value) {
Float currentKeyframe = getSelectedKeyframe(false);
broadcast(modelPart, paramId, value, currentKeyframe);
}
/**
* 移除特定参数
* @param modelPart 部件
@@ -321,7 +425,7 @@ public class ParametersManagement {
}
sb.append(String.format("{ID=%s, V=%s, KF=%s, IsKF=%b}",
id,
String.valueOf(val),
val,
kf != null ? String.valueOf(kf) : "null",
isKf));
}

View File

@@ -1254,38 +1254,8 @@ public class SelectionTool extends Tool {
updateMeshVertices(hoveredMesh);
}
@SuppressWarnings("unchecked")
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
if (selected && !isRenderVertices) {
RenderSystem.pushState();
RenderSystem.enableBlend();
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.pushState();
try {
setSolidShader(modelMatrix);
if (isInMultiSelection()) {
drawMultiSelectionBox();
} else {

View File

@@ -1608,9 +1608,18 @@ public class ModelPart {
for (Mesh2D mesh : meshes) {
if (mesh == null) continue;
for (Vertex vertex : mesh.getActiveVertexList()) {
Vertex oldVertexCopy = vertex.copy();
Vector2f localPoint = vertex.originalPosition;
Vector2f worldPoint = Matrix3fUtils.transformPoint(this.worldTransform, localPoint);
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();
}