refactor(render): 优化顶点更新与选框渲染逻辑
- 调整 Mesh2D 中选中框绘制的状态压栈顺序,确保渲染状态正确恢复 - 在 ModelPart 更新顶点位置后触发 vertex 事件,携带新旧顶点信息 - 简化 MultiSelectionBoxRenderer 的边框绘制逻辑,移除多层边框结构 - 使用 GL_LINES 模式重写选框边界绘制,提升性能与清晰度 - 修改 ParametersManagement 监听 vertex 事件并实现顶点关键帧更新逻辑 - 为 broadcast 方法增加指定关键帧参数重载,增强灵活性 - 移除 SelectionTool 中冗余的网格顶点更新方法体,待后续重构
This commit is contained in:
@@ -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)];
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user