diff --git a/src/main/java/com/chuangzhou/vivid2D/render/MultiSelectionBoxRenderer.java b/src/main/java/com/chuangzhou/vivid2D/render/MultiSelectionBoxRenderer.java index 7acbeb7..f367d55 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/MultiSelectionBoxRenderer.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/MultiSelectionBoxRenderer.java @@ -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)]; diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ParametersManagement.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ParametersManagement.java index f7710e4..1c46a73 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ParametersManagement.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/manager/ParametersManagement.java @@ -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 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 eventPayload = (Map) 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 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 payload = (Map) 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 newVertexUpdatePayload = Map.of( + "id", newVertexObj.getName(), + "Vertex", new float[]{newVertexObj.position.x, newVertexObj.position.y} + ); + List 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 newValues = new ArrayList<>(existingParameter.value()); List newKeyframes = new ArrayList<>(existingParameter.keyframe()); List 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)); } diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java index 505d199..94149a6 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/SelectionTool.java @@ -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 paramValue = param.value(); - for (Object keyframeDataObject : paramValue) { - if (!(keyframeDataObject instanceof Map)) { - continue; - } - Map keyframeData = (Map) 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 vertexUpdatePayload = Map.of( - "id", vertex.getName(), - "Vertex", new float[]{vertex.position.x, vertex.position.y} - ); - renderPanel.getParametersManagement().broadcast(mesh.getModelPart(), "meshVertices", vertexUpdatePayload); - } - } - } + // } /** diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/Mesh2D.java b/src/main/java/com/chuangzhou/vivid2D/render/model/Mesh2D.java index 57b1a7a..cad7db2 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/Mesh2D.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/Mesh2D.java @@ -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 { diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java index 79aab19..84a3ae4 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java @@ -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 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(); }