From f2cb74379ef08446d9e940cfae44ee71cb820714 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sun, 26 Oct 2025 18:37:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(render):=20=E5=AE=9E=E7=8E=B0=E7=BD=91?= =?UTF-8?q?=E6=A0=BC=E9=A1=B6=E7=82=B9=E9=A2=84=E6=B5=8B=E4=B8=8E=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E7=82=B9=E4=BC=98=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 previewPoint 字段支持预览点显示 - 实现 predictVerticesWithTemporarySecondary 方法用于顶点变形预测 - 引入 SNAP_THRESHOLD 和 pinnedController 支持控制点吸附逻辑- 优化 updateVerticesFromSecondaryVertices 方法的三角分配策略 - 添加 moveSecondaryVertex 方法支持控制点移动与锁定逻辑 - 集成 RegionOptimizer 优化控制点半径分配- 移除冗余的 liquify 和 puppet 渲染代码进网格 - 改变形算法稳定性与性能表现 --- .../awt/tools/VertexDeformationTool.java | 34 +- .../vivid2D/render/model/ModelPart.java | 10 +- .../vivid2D/render/model/data/MeshData.java | 25 + .../vivid2D/render/model/util/Mesh2D.java | 1118 ++++++++--------- .../vivid2D/render/model/util/PuppetPin.java | 42 +- .../render/model/util/RegionOptimizer.java | 191 +++ .../render/model/util/SecondaryVertex.java | 67 +- .../util/tools/PuppetDeformationRander.java | 281 +++++ .../util/tools/VertexDeformationRander.java | 432 +++++++ .../vivid2D/render/systems/RenderSystem.java | 199 +-- src/main/java/org/tzd/debug/GetInstance.class | Bin 0 -> 337 bytes .../org/tzd/debug/org_tzd_debug_GetInstance.h | 21 + 12 files changed, 1693 insertions(+), 727 deletions(-) create mode 100644 src/main/java/com/chuangzhou/vivid2D/render/model/util/RegionOptimizer.java create mode 100644 src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/PuppetDeformationRander.java create mode 100644 src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/VertexDeformationRander.java create mode 100644 src/main/java/org/tzd/debug/GetInstance.class create mode 100644 src/main/java/org/tzd/debug/org_tzd_debug_GetInstance.h diff --git a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java index 7574ba1..7c294ef 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/awt/tools/VertexDeformationTool.java @@ -54,9 +54,9 @@ public class VertexDeformationTool extends Tool { targetMesh.setRenderVertices(true); // 如果没有二级顶点,创建默认的四个角点 - if (targetMesh.getSecondaryVertexCount() == 0) { - createDefaultSecondaryVertices(); - } + //if (targetMesh.getSecondaryVertexCount() == 0) { + // createDefaultSecondaryVertices(); + //} logger.info("激活顶点变形工具: {}", targetMesh.getName()); } else { @@ -220,34 +220,6 @@ public class VertexDeformationTool extends Tool { return null; } - /** - * 创建默认的四个角点二级顶点 - */ - private void createDefaultSecondaryVertices() { - if (targetMesh == null) return; - - // 确保边界框是最新的 - targetMesh.updateBounds(); - BoundingBox bounds = targetMesh.getBounds(); - if (bounds == null || !bounds.isValid()) { - logger.warn("无法为网格 {} 创建默认二级顶点:边界框无效", targetMesh.getName()); - return; - } - - float minX = bounds.getMinX(); - float minY = bounds.getMinY(); - float maxX = bounds.getMaxX(); - float maxY = bounds.getMaxY(); - - // 创建四个角点 - targetMesh.addSecondaryVertex(minX, minY, 0.0f, 1.0f); // 左下 - targetMesh.addSecondaryVertex(maxX, minY, 1.0f, 1.0f); // 右下 - targetMesh.addSecondaryVertex(maxX, maxY, 1.0f, 0.0f); // 右上 - targetMesh.addSecondaryVertex(minX, maxY, 0.0f, 0.0f); // 左上 - - logger.debug("为网格 {} 创建了4个默认二级顶点", targetMesh.getName()); - } - /** * 在指定位置创建二级顶点 */ 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 e50cad2..61dbbb2 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java @@ -1692,20 +1692,20 @@ public class ModelPart { } for (PuppetPin pin : puppetPins) { - // 获取控制点的原始局部位置 + // 获取控制点的原始局部位置(保持不变!) Vector2f originalLocalPos = pin.getOriginalPosition(); - // 将原始局部位置变换到世界坐标 + // 将原始局部位置变换到世界坐标(用于显示) Vector2f worldPos = Matrix3fUtils.transformPoint(worldTransform, originalLocalPos); - // 更新控制点的当前位置 + // 只更新控制点的显示位置,保持原始局部位置不变 pin.setPosition(worldPos.x, worldPos.y); - logger.trace("更新木偶控制点位置: 局部({}, {}) -> 世界({}, {})", + logger.trace("更新木偶控制点显示位置: 局部({}, {}) -> 世界({}, {})", originalLocalPos.x, originalLocalPos.y, worldPos.x, worldPos.y); } - logger.debug("同步更新了 {} 个木偶控制点的位置", puppetPins.size()); + logger.debug("同步更新了 {} 个木偶控制点的显示位置", puppetPins.size()); } public void setPosition(Vector2f pos) { diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java b/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java index ab7f3fb..ea6a339 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/data/MeshData.java @@ -6,6 +6,7 @@ import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex; import org.joml.Vector2f; import org.joml.Vector4f; +import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; @@ -278,6 +279,7 @@ public class MeshData implements Serializable { * 二级顶点数据类(可序列化) */ public static class SecondaryVertexData implements Serializable { + @Serial private static final long serialVersionUID = 1L; public int id; @@ -285,6 +287,12 @@ public class MeshData implements Serializable { public Vector2f originalPosition; public Vector2f uv; public boolean selected; + public boolean pinned; + public boolean locked; + public float controlRadius; + public float minControlRadius; + public float maxControlRadius; + public boolean fixedRadius; public SecondaryVertexData() { } @@ -295,6 +303,13 @@ public class MeshData implements Serializable { this.originalPosition = new Vector2f(vertex.getOriginalPosition()); this.uv = new Vector2f(vertex.getUV()); this.selected = vertex.isSelected(); + + this.pinned = vertex.isPinned(); + this.locked = vertex.isLocked(); + this.controlRadius = vertex.getControlRadius(); + this.minControlRadius = vertex.getMinControlRadius(); + this.maxControlRadius = vertex.getMaxControlRadius(); + this.fixedRadius = vertex.isFixedRadius(); } public SecondaryVertexData copy() { @@ -304,6 +319,15 @@ public class MeshData implements Serializable { copy.originalPosition = new Vector2f(this.originalPosition); copy.uv = new Vector2f(this.uv); copy.selected = this.selected; + + // 复制新增字段 + copy.pinned = this.pinned; + copy.locked = this.locked; + copy.controlRadius = this.controlRadius; + copy.minControlRadius = this.minControlRadius; + copy.maxControlRadius = this.maxControlRadius; + copy.fixedRadius = this.fixedRadius; + return copy; } } @@ -312,6 +336,7 @@ public class MeshData implements Serializable { * 木偶控制点数据类(可序列化) */ public static class PuppetPinData implements Serializable { + @Serial private static final long serialVersionUID = 1L; public int id; diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java index 3315b52..4fdf5e6 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java @@ -4,6 +4,7 @@ import com.chuangzhou.vivid2D.render.ModelRender; import com.chuangzhou.vivid2D.render.MultiSelectionBoxRenderer; import com.chuangzhou.vivid2D.render.TextRenderer; import com.chuangzhou.vivid2D.render.model.ModelPart; +import com.chuangzhou.vivid2D.render.model.util.manager.RanderToolsManager; import com.chuangzhou.vivid2D.render.systems.RenderSystem; import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder; import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator; @@ -80,6 +81,7 @@ public class Mesh2D { // ==================== 液化状态渲染 ==================== public boolean showLiquifyOverlay = false; private final Vector4f liquifyOverlayColor = new Vector4f(1.0f, 0.5f, 0.0f, 0.3f); // 半透明橙色 + private Vector2f previewPoint = null; // ==================== 木偶工具 ==================== private final List puppetPins = new ArrayList<>(); @@ -99,6 +101,8 @@ public class Mesh2D { private static final float ROTATION_HANDLE_DISTANCE = 30.0f; // ==================== 构造器 ==================== + private static final float SNAP_THRESHOLD = 0.01f; // 靠近判定阈值(按需要调大,比如 0.1f) + private SecondaryVertex pinnedController = null; // 当前作为“钉子”的控制点(若有) public Mesh2D() { this("unnamed"); } @@ -175,6 +179,15 @@ public class Mesh2D { return isRenderVertices; } + public void setPreviewPoint(Vector2f p) { + if (p == null) this.previewPoint = null; + else this.previewPoint = new Vector2f(p); + } + + public Vector2f getPreviewPoint() { + return previewPoint == null ? null : new Vector2f(previewPoint); + } + /** * 添加木偶控制点 */ @@ -204,6 +217,108 @@ public class Mesh2D { return removed; } + /** + * 预测:在不修改当前 mesh 的情况下,如果在 tempPos 放一个控制点(半径 tempRadius) + * 将会得到的顶点数组(与 vertices 长度相同的新数组)。 + * 使用与 updateVerticesFromSecondaryVertices 类似的三角分配策略进行预测(回退到 IDW)。 + */ + public float[] predictVerticesWithTemporarySecondary(org.joml.Vector2f tempPos, float tempRadius) { + if (originalVertices == null || originalVertices.length == 0) return null; + + int secCount = (secondaryVertices == null) ? 0 : secondaryVertices.size(); + // 构建临时控制点数组(原始位置 + 当前位置) + int tmpCount = secCount + 1; // 包含临时点 + org.joml.Vector2f[] secOrig = new org.joml.Vector2f[tmpCount]; + org.joml.Vector2f[] secCurr = new org.joml.Vector2f[tmpCount]; + float[] secRadius = new float[tmpCount]; + + for (int i = 0; i < secCount; i++) { + SecondaryVertex sv = secondaryVertices.get(i); + secOrig[i] = sv.getOriginalPosition(); + secCurr[i] = sv.getPosition(); + secRadius[i] = sv.getControlRadius(); + } + // 最后一个为临时点 + secOrig[tmpCount - 1] = (previewPoint != null && previewPoint.equals(tempPos)) ? tempPos : new org.joml.Vector2f(tempPos); + secCurr[tmpCount - 1] = new org.joml.Vector2f(tempPos); + secRadius[tmpCount - 1] = Math.max(4f, tempRadius); + + // 结果数组(不修改实际 vertices) + float[] pred = new float[originalVertices.length]; + + // 辅助:重用类内的 findNearestNIndices/pointInTriangle/barycentricCoordinates(如果存在) + // 若这些方法是 private 且在同一类中可以直接调用;若不存在则使用简单回退 IDW(这里假设存在) + try { + for (int i = 0; i < originalVertices.length; i += 2) { + float ox = originalVertices[i]; + float oy = originalVertices[i + 1]; + + // 找到最近三个控制点(优先考虑 controlRadius 内的点) + int[] nearest = findNearestNIndices(ox, oy, 3, secOrig); + if (nearest == null || nearest.length < 3) { + // 回退到反距离加权预测(简单实现,不依赖外部私有方法) + float power = 2.0f; + float eps = 1e-4f; + float wsum = 0f; + float nx = 0f, ny = 0f; + for (int k = 0; k < tmpCount; k++) { + float dx = ox - secOrig[k].x; + float dy = oy - secOrig[k].y; + float d = (float)Math.sqrt(dx*dx + dy*dy); + if (d < eps) { nx = secCurr[k].x; ny = secCurr[k].y; wsum = 1f; break; } + float w = 1.0f / (float)Math.pow(d, power); + float deltaX = secCurr[k].x - secOrig[k].x; + float deltaY = secCurr[k].y - secOrig[k].y; + nx += w * (ox + deltaX); + ny += w * (oy + deltaY); + wsum += w; + } + if (wsum > 0f) { + pred[i] = nx / wsum; + pred[i + 1] = ny / wsum; + } else { + pred[i] = ox; pred[i + 1] = oy; + } + continue; + } + + int ia = nearest[0], ib = nearest[1], ic = nearest[2]; + org.joml.Vector2f A = secOrig[ia], B = secOrig[ib], C = secOrig[ic]; + // 检测退化 + float area2 = Math.abs((B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y)); + if (area2 < 1e-6f) { + // 退化:取最近控制点位移 + int nearestIdx = nearest[0]; + org.joml.Vector2f sOrig = secOrig[nearestIdx]; + org.joml.Vector2f sCurr = secCurr[nearestIdx]; + pred[i] = ox + (sCurr.x - sOrig.x); + pred[i + 1] = oy + (sCurr.y - sOrig.y); + continue; + } + + if (pointInTriangle(ox, oy, A, B, C)) { + float[] bary = barycentricCoordinates(A, B, C, ox, oy); + org.joml.Vector2f Acur = secCurr[ia], Bcur = secCurr[ib], Ccur = secCurr[ic]; + float nx = bary[0] * Acur.x + bary[1] * Bcur.x + bary[2] * Ccur.x; + float ny = bary[0] * Acur.y + bary[1] * Bcur.y + bary[2] * Ccur.y; + pred[i] = nx; pred[i + 1] = ny; + } else { + // 点不在三角形内:使用最近控制点位移 + int nearestIdx = nearest[0]; + org.joml.Vector2f sOrig = secOrig[nearestIdx]; + org.joml.Vector2f sCurr = secCurr[nearestIdx]; + pred[i] = ox + (sCurr.x - sOrig.x); + pred[i + 1] = oy + (sCurr.y - sOrig.y); + } + } + } catch (Exception ex) { + // 出错时直接返回当前顶点数组的拷贝(不变) + pred = java.util.Arrays.copyOf(vertices, vertices.length); + } + + return pred; + } + /** * 设置选中的木偶控制点 */ @@ -308,14 +423,11 @@ public class Mesh2D { return; } - // 保存原始顶点位置(如果还没有保存) - if (originalVertices.length != vertices.length) { - originalVertices = vertices.clone(); + // 只有在没有活跃变形时才重置到原始位置 + if (!hasActivePuppetDeformation()) { + System.arraycopy(originalVertices, 0, vertices, 0, vertices.length); } - // 重置顶点到原始位置 - System.arraycopy(originalVertices, 0, vertices, 0, vertices.length); - // 应用所有控制点的变形 boolean hasDeformation = false; for (PuppetPin pin : puppetPins) { @@ -325,7 +437,7 @@ public class Mesh2D { } if (hasDeformation) { - markDirtyForPuppet(); // 使用专门的方法标记脏状态 + markDirtyForPuppet(); } } @@ -380,167 +492,6 @@ public class Mesh2D { return showPuppetPins; } - /** - * 绘制木偶控制点 - */ - private void drawPuppetPins(Matrix3f modelMatrix) { - if (!showPuppetPins || puppetPins.isEmpty()) return; - - RenderSystem.pushState(); - try { - ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader"); - if (solidShader != null && solidShader.programId != 0) { - solidShader.use(); - - // 设置模型矩阵 - int modelLoc = solidShader.getUniformLocation("uModelMatrix"); - if (modelLoc != -1) { - RenderSystem.uniformMatrix3(modelLoc, modelMatrix); - } - } - - RenderSystem.enableBlend(); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - - Tesselator t = Tesselator.getInstance(); - BufferBuilder bb = t.getBuilder(); - - // 绘制控制点影响范围 - drawPuppetPinInfluenceRanges(bb); - - // 绘制控制点 - for (PuppetPin pin : puppetPins) { - Vector2f position = pin.getPosition(); - Vector4f color = pin.isSelected() ? selectedPuppetPinColor : puppetPinColor; - - drawPuppetPin(bb, position.x, position.y, color, puppetPinSize, pin.isSelected()); - - // 为选中的控制点绘制信息 - if (pin.isSelected()) { - drawPuppetPinInfo(bb, pin, position.x, position.y); - } - } - - } finally { - RenderSystem.popState(); - } - } - - /** - * 绘制控制点影响范围 - */ - private void drawPuppetPinInfluenceRanges(BufferBuilder bb) { - for (PuppetPin pin : puppetPins) { - Vector2f position = pin.getPosition(); - float radius = pin.getInfluenceRadius(); - Vector4f rangeColor = new Vector4f(0.3f, 0.3f, 1.0f, 0.2f); // 半透明蓝色 - - // 绘制影响范围圆圈 - bb.begin(GL11.GL_TRIANGLE_FAN, 32); - bb.setColor(rangeColor); - bb.vertex(position.x, position.y, 0f, 0f); // 中心点 - - for (int i = 0; i <= 32; i++) { - float angle = (float) (i * 2 * Math.PI / 32); - float x = position.x + (float) Math.cos(angle) * radius; - float y = position.y + (float) Math.sin(angle) * radius; - bb.vertex(x, y, 0f, 0f); - } - bb.endImmediate(); - - // 绘制范围边框 - bb.begin(GL11.GL_LINE_LOOP, 32); - bb.setColor(new Vector4f(0.1f, 0.1f, 0.8f, 0.6f)); - - for (int i = 0; i < 32; i++) { - float angle = (float) (i * 2 * Math.PI / 32); - float x = position.x + (float) Math.cos(angle) * radius; - float y = position.y + (float) Math.sin(angle) * radius; - bb.vertex(x, y, 0f, 0f); - } - bb.endImmediate(); - } - } - - /** - * 绘制木偶控制点(更醒目的样式) - */ - private void drawPuppetPin(BufferBuilder bb, float x, float y, Vector4f color, float size, boolean selected) { - float halfSize = size / 2; - - if (selected) { - // 选中的控制点:带圆圈的十字 - bb.begin(GL11.GL_LINES, 4); - bb.setColor(color); - bb.vertex(x - halfSize, y, 0f, 0f); - bb.vertex(x + halfSize, y, 0f, 0f); - bb.vertex(x, y - halfSize, 0f, 0f); - bb.vertex(x, y + halfSize, 0f, 0f); - bb.endImmediate(); - - // 外圈圆圈 - bb.begin(GL11.GL_LINE_LOOP, 16); - bb.setColor(color); - float circleSize = size * 1.5f; - for (int i = 0; i < 16; i++) { - float angle = (float) (i * 2 * Math.PI / 16); - float cx = x + (float) Math.cos(angle) * circleSize; - float cy = y + (float) Math.sin(angle) * circleSize; - bb.vertex(cx, cy, 0f, 0f); - } - bb.endImmediate(); - } else { - // 普通控制点:实心圆圈 - bb.begin(GL11.GL_TRIANGLE_FAN, 16); - bb.setColor(color); - bb.vertex(x, y, 0f, 0f); // 中心点 - for (int i = 0; i <= 16; i++) { - float angle = (float) (i * 2 * Math.PI / 16); - float cx = x + (float) Math.cos(angle) * halfSize; - float cy = y + (float) Math.sin(angle) * halfSize; - bb.vertex(cx, cy, 0f, 0f); - } - bb.endImmediate(); - - // 边框 - bb.begin(GL11.GL_LINE_LOOP, 16); - bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); - for (int i = 0; i < 16; i++) { - float angle = (float) (i * 2 * Math.PI / 16); - float cx = x + (float) Math.cos(angle) * halfSize; - float cy = y + (float) Math.sin(angle) * halfSize; - bb.vertex(cx, cy, 0f, 0f); - } - bb.endImmediate(); - } - } - - /** - * 绘制控制点信息 - */ - private void drawPuppetPinInfo(BufferBuilder bb, PuppetPin pin, float x, float y) { - String infoText = pin.getName() + " (R:" + (int) pin.getInfluenceRadius() + ")"; - TextRenderer textRenderer = ModelRender.getTextRenderer(); - if (textRenderer != null) { - float textWidth = textRenderer.getTextWidth(infoText); - float textX = x + puppetPinSize + 5.0f; - float textY = y - 6.0f; - - // 绘制文字背景 - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f)); - bb.vertex(textX - 3, textY - 10, 0f, 0f); - bb.vertex(textX + textWidth + 3, textY - 10, 0f, 0f); - bb.vertex(textX + textWidth + 3, textY + 4, 0f, 0f); - bb.vertex(textX + textWidth + 3, textY + 4, 0f, 0f); - bb.vertex(textX - 3, textY + 4, 0f, 0f); - bb.vertex(textX - 3, textY - 10, 0f, 0f); - bb.endImmediate(); - - // 绘制文字 - ModelRender.renderText(infoText, textX, textY, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); - } - } // ==================== 网格数据设置 ==================== @@ -589,328 +540,6 @@ public class Mesh2D { markDirty(); } - /** - * 绘制液化状态覆盖层 - 渲染所有顶点 - */ - private void drawLiquifyOverlay(int shaderProgram, Matrix3f modelMatrix) { - if (!showLiquifyOverlay) return; - - RenderSystem.pushState(); - try { - // 使用固色着色器 - ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader"); - if (solidShader != null && solidShader.programId != 0) { - solidShader.use(); - - // 设置模型矩阵 - int modelLoc = solidShader.getUniformLocation("uModelMatrix"); - if (modelLoc != -1) { - RenderSystem.uniformMatrix3(modelLoc, modelMatrix); - } - - // 设置液化覆盖层颜色 - int colorLoc = solidShader.getUniformLocation("uColor"); - if (colorLoc != -1) { - RenderSystem.uniform4f(colorLoc, liquifyOverlayColor); - } - } - - RenderSystem.enableBlend(); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - - Tesselator t = Tesselator.getInstance(); - BufferBuilder bb = t.getBuilder(); - - if (isRenderVertices && vertices != null && vertices.length >= 6) { - // ============ 显示顶点模式 ============ - // 1. 绘制所有顶点组成的多边形填充 - bb.begin(GL11.GL_TRIANGLES, vertices.length / 2 * 3); - bb.setColor(liquifyOverlayColor); - - // 使用三角形扇绘制填充 - Vector2f center = new Vector2f(vertices[0], vertices[1]); - for (int i = 1; i < vertices.length / 2 - 1; i++) { - int baseIndex1 = i * 2; - int baseIndex2 = (i + 1) * 2; - - bb.vertex(center.x, center.y, 0f, 0f); - bb.vertex(vertices[baseIndex1], vertices[baseIndex1 + 1], 0f, 0f); - bb.vertex(vertices[baseIndex2], vertices[baseIndex2 + 1], 0f, 0f); - } - t.end(); - - // 2. 绘制顶点连线(边框) - drawLiquifyVertexLines(bb); - - // 3. 绘制顶点标记 - drawLiquifyVertexPoints(bb); - - // 4. 绘制液化提示文字 - drawLiquifyTextAtCenter(bb); - - } else { - // 4. 绘制液化状态指示器 - drawLiquifyStatusIndicator(bb); - } - - } finally { - RenderSystem.popState(); - } - } - - /** - * 绘制液化状态指示器(在不显示顶点时) - */ - private void drawLiquifyStatusIndicator(BufferBuilder bb) { - BoundingBox bounds = getBounds(); - if (bounds == null || !bounds.isValid()) return; - - float centerX = (bounds.getMinX() + bounds.getMaxX()) / 2.0f; - float centerY = (bounds.getMinY() + bounds.getMaxY()) / 2.0f; - float width = bounds.getWidth(); - float height = bounds.getHeight(); - - // 计算指示器位置(放在网格右上方,不遮挡内容) - float indicatorX = bounds.getMaxX() + Math.max(width, height) * 0.2f; - float indicatorY = bounds.getMaxY() + Math.max(width, height) * 0.1f; - - // 1. 绘制简洁的液化状态圆点 - float dotRadius = Math.max(width, height) * 0.08f; - - // 外圈圆点 - bb.begin(GL11.GL_TRIANGLE_FAN, 16); - bb.setColor(new Vector4f(1.0f, 0.6f, 0.0f, 0.8f)); // 橙色圆点 - bb.vertex(indicatorX, indicatorY, 0f, 0f); // 中心点 - for (int i = 0; i <= 16; i++) { - float angle = (float) (i * 2 * Math.PI / 16); - float x = indicatorX + (float) Math.cos(angle) * dotRadius; - float y = indicatorY + (float) Math.sin(angle) * dotRadius; - bb.vertex(x, y, 0f, 0f); - } - bb.endImmediate(); - - // 内圈白色圆点 - float innerRadius = dotRadius * 0.5f; - bb.begin(GL11.GL_TRIANGLE_FAN, 12); - bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 0.9f)); // 白色内圆 - bb.vertex(indicatorX, indicatorY, 0f, 0f); // 中心点 - for (int i = 0; i <= 12; i++) { - float angle = (float) (i * 2 * Math.PI / 12); - float x = indicatorX + (float) Math.cos(angle) * innerRadius; - float y = indicatorY + (float) Math.sin(angle) * innerRadius; - bb.vertex(x, y, 0f, 0f); - } - bb.endImmediate(); - - // 2. 绘制简洁的画笔图标 - drawSimpleBrushIcon(bb, indicatorX, indicatorY, dotRadius); - - // 3. 绘制简洁的提示文字 - String liquifyText = "Liquify"; - String hintText = "Ctrl: Show Vertices"; - - TextRenderer textRenderer = ModelRender.getTextRenderer(); - if (textRenderer != null) { - float textY = indicatorY + dotRadius + 15f; - - // 主标题 - float titleWidth = textRenderer.getTextWidth(liquifyText); - float titleX = indicatorX - titleWidth / 2.0f; - - // 绘制主标题背景(简洁的圆角效果) - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(new Vector4f(0.0f, 0.0f, 0.0f, 0.6f)); // 半透明黑色背景 - bb.vertex(titleX - 6, textY - 12, 0f, 0f); - bb.vertex(titleX + titleWidth + 6, textY - 12, 0f, 0f); - bb.vertex(titleX + titleWidth + 6, textY + 2, 0f, 0f); - bb.vertex(titleX + titleWidth + 6, textY + 2, 0f, 0f); - bb.vertex(titleX - 6, textY + 2, 0f, 0f); - bb.vertex(titleX - 6, textY - 12, 0f, 0f); - bb.endImmediate(); - - // 绘制主标题 - ModelRender.renderText(liquifyText, titleX, textY, new Vector4f(1.0f, 0.8f, 0.0f, 1.0f)); - - // 提示文字(小字号) - float hintY = textY + 15f; - float hintWidth = textRenderer.getTextWidth(hintText); - float hintX = indicatorX - hintWidth / 2.0f; - - ModelRender.renderText(hintText, hintX, hintY, new Vector4f(0.8f, 0.8f, 0.8f, 0.7f)); - } - } - - /** - * 绘制简洁的画笔图标 - */ - private void drawSimpleBrushIcon(BufferBuilder bb, float centerX, float centerY, float size) { - float iconSize = size * 0.6f; - - // 画笔柄(简单的线条) - bb.begin(GL11.GL_LINES, 2); - bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 0.9f)); - bb.vertex(centerX - iconSize * 0.3f, centerY - iconSize * 0.5f, 0f, 0f); - bb.vertex(centerX, centerY + iconSize * 0.3f, 0f, 0f); - bb.endImmediate(); - - // 画笔头(小三角形) - bb.begin(GL11.GL_TRIANGLES, 3); - bb.setColor(new Vector4f(1.0f, 0.3f, 0.0f, 0.9f)); - bb.vertex(centerX, centerY + iconSize * 0.3f, 0f, 0f); - bb.vertex(centerX - iconSize * 0.2f, centerY + iconSize * 0.6f, 0f, 0f); - bb.vertex(centerX + iconSize * 0.2f, centerY + iconSize * 0.6f, 0f, 0f); - bb.endImmediate(); - } - - /** - * 绘制顶点连线(边框) - */ - private void drawLiquifyVertexLines(BufferBuilder bb) { - final Vector4f LINE_COLOR = new Vector4f(1.0f, 0.8f, 0.0f, 1.0f); // 黄色边框 - - if (vertices == null || vertices.length < 4) return; - - // 绘制闭合的多边形边框 - bb.begin(GL11.GL_LINE_LOOP, vertices.length / 2); - bb.setColor(LINE_COLOR); - - for (int i = 0; i < vertices.length / 2; i++) { - int baseIndex = i * 2; - bb.vertex(vertices[baseIndex], vertices[baseIndex + 1], 0f, 0f); - } - bb.endImmediate(); - - // 如果网格有三角形索引,也绘制内部连线 - if (indices != null && indices.length >= 3) { - bb.begin(GL11.GL_LINES, indices.length * 2); - bb.setColor(new Vector4f(1.0f, 0.6f, 0.0f, 0.6f)); // 半透明橙色内部线 - - for (int i = 0; i < indices.length; i += 3) { - int i1 = indices[i]; - int i2 = indices[i + 1]; - int i3 = indices[i + 2]; - - // 三条边 - drawLineBetweenVertices(bb, i1, i2); - drawLineBetweenVertices(bb, i2, i3); - drawLineBetweenVertices(bb, i3, i1); - } - bb.endImmediate(); - } - } - - /** - * 在两个顶点之间绘制线段 - */ - private void drawLineBetweenVertices(BufferBuilder bb, int index1, int index2) { - if (index1 < 0 || index1 >= vertices.length / 2 || - index2 < 0 || index2 >= vertices.length / 2) { - return; - } - - int base1 = index1 * 2; - int base2 = index2 * 2; - - bb.vertex(vertices[base1], vertices[base1 + 1], 0f, 0f); - bb.vertex(vertices[base2], vertices[base2 + 1], 0f, 0f); - } - - /** - * 绘制顶点标记 - */ - private void drawLiquifyVertexPoints(BufferBuilder bb) { - final float POINT_SIZE = 4.0f; - final Vector4f POINT_COLOR = new Vector4f(1.0f, 0.4f, 0.0f, 1.0f); // 橙色顶点 - - if (vertices == null) return; - - // 绘制所有顶点 - for (int i = 0; i < vertices.length / 2; i++) { - int baseIndex = i * 2; - float x = vertices[baseIndex]; - float y = vertices[baseIndex + 1]; - - // 绘制小方块表示顶点 - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(POINT_COLOR); - - float halfSize = POINT_SIZE / 2; - bb.vertex(x - halfSize, y - halfSize, 0f, 0f); - bb.vertex(x + halfSize, y - halfSize, 0f, 0f); - bb.vertex(x + halfSize, y + halfSize, 0f, 0f); - - bb.vertex(x + halfSize, y + halfSize, 0f, 0f); - bb.vertex(x - halfSize, y + halfSize, 0f, 0f); - bb.vertex(x - halfSize, y - halfSize, 0f, 0f); - - bb.endImmediate(); - - // 在顶点旁边显示编号(可选) - drawVertexNumber(bb, i, x, y); - } - } - - /** - * 在顶点旁边显示编号 - */ - private void drawVertexNumber(BufferBuilder bb, int vertexIndex, float x, float y) { - String numberText = String.valueOf(vertexIndex); - TextRenderer textRenderer = ModelRender.getTextRenderer(); - if (textRenderer != null) { - float textWidth = textRenderer.getTextWidth(numberText); - float textX = x + 6.0f; // 在顶点右侧显示 - float textY = y - 4.0f; - - // 绘制文字背景 - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f)); - bb.vertex(textX - 2, textY - 8, 0f, 0f); - bb.vertex(textX + textWidth + 2, textY - 8, 0f, 0f); - bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f); - bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f); - bb.vertex(textX - 2, textY + 2, 0f, 0f); - bb.vertex(textX - 2, textY - 8, 0f, 0f); - bb.endImmediate(); - - // 绘制文字 - ModelRender.renderText(numberText, textX, textY, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); - } - } - - /** - * 在网格中心绘制液化提示文字 - */ - private void drawLiquifyTextAtCenter(BufferBuilder bb) { - String liquifyText = "LIQUIFY MODE"; - BoundingBox bounds = getBounds(); - if (bounds == null || !bounds.isValid()) return; - - float centerX = (bounds.getMinX() + bounds.getMaxX()) / 2.0f; - float textY = bounds.getMaxY() + 20.0f; - - // 使用文本渲染器绘制提示文字 - Vector4f textColor = new Vector4f(1.0f, 0.8f, 0.0f, 1.0f); - TextRenderer textRenderer = ModelRender.getTextRenderer(); - if (textRenderer != null) { - float textWidth = textRenderer.getTextWidth(liquifyText); - float textX = centerX - textWidth / 2.0f; - - // 绘制文字背景 - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f)); - bb.vertex(textX - 5, textY - 15, 0f, 0f); - bb.vertex(textX + textWidth + 5, textY - 15, 0f, 0f); - bb.vertex(textX + textWidth + 5, textY + 5, 0f, 0f); - bb.vertex(textX + textWidth + 5, textY + 5, 0f, 0f); - bb.vertex(textX - 5, textY + 5, 0f, 0f); - bb.vertex(textX - 5, textY - 15, 0f, 0f); - bb.endImmediate(); - - // 绘制文字 - ModelRender.renderText(liquifyText, textX, textY, textColor); - } - } - /** * 设置中心点 */ @@ -1189,6 +818,124 @@ public class Mesh2D { return vertex; } + /** + * 移动某个二级顶点(已改动:增加调用 RegionOptimizer,重新分配 controlRadius) + */ + public void moveSecondaryVertex(SecondaryVertex v, float newX, float newY) { + if (v == null) return; + + if (v.isLocked()) { + // 已锁定,不能移动 + logger.debug("secondary vertex {} is locked, move ignored", v.getId()); + return; + } + + // 如果 v 已经被 pin,则将整个网格按 delta 平移 + Vector2f oldPos = v.getPosition(); + if (v.isPinned()) { + float dx = newX - oldPos.x; + float dy = newY - oldPos.y; + applyDeltaToMesh(dx, dy); + logger.debug("moved pinned vertex {} by delta ({}, {}) and translated whole mesh", v.getId(), dx, dy); + return; + } + + // 否则我们尝试正常移动,同时检查是否碰撞到其他顶点(snap) + // 优先检测是否靠近其他 existing 顶点位置 + for (SecondaryVertex other : secondaryVertices) { + if (other == v) continue; + Vector2f otherPos = other.getPosition(); + if (isClose(newX, newY, otherPos.x, otherPos.y, SNAP_THRESHOLD)) { + // Snap 到 other + v.setPosition(otherPos); + // 把被靠近的那个当作钉子 + other.setPinned(true); + pinnedController = other; + // 把移动到其上的顶点锁定,不能再单独移动 + v.setLocked(true); + logger.info("SecondaryVertex {} snapped to {}. {} pinned, {} locked.", v.getId(), other.getId(), other.getId(), v.getId()); + return; + } + } + + // 没有 snap,正常移动该点(只移动当前顶点,不影响整块) + v.setPosition(newX, newY); + logger.debug("Moved secondary vertex {} to ({}, {})", v.getId(), newX, newY); + } + + /** + * 判断两点是否接近(用于 snap 判定) + */ + private boolean isClose(float ax, float ay, float bx, float by, float thresh) { + float dx = ax - bx; + float dy = ay - by; + return dx * dx + dy * dy <= thresh * thresh; + } + + /** + * 取消所有 pinned/locked 状态(比如用户按下某个解锁命令) + */ + public void unpinAll() { + for (SecondaryVertex sv : secondaryVertices) { + sv.setPinned(false); + sv.setLocked(false); + } + pinnedController = null; + logger.info("All secondary vertices unpinned/unlocked"); + } + + /** + * 把整个网格按 dx,dy 平移(用于移动 pinned 顶点时) + * 会移动: + * - 所有 secondaryVertices 的 position & originalPosition + * - 顶点数组 vertices 与 originalVertices + */ + private void applyDeltaToMesh(float dx, float dy) { + if (dx == 0f && dy == 0f) return; + + // 移动 secondary vertices(当前 pos 和 original pos 都平移) + if (secondaryVertices != null) { + for (SecondaryVertex sv : secondaryVertices) { + Vector2f p = sv.getPosition(); + p.add(dx, dy); + sv.setPosition(p); + + Vector2f op = sv.getOriginalPosition(); + op.add(dx, dy); + sv.setOriginalPosition(op); + } + } + + // 移动顶点数组(当前和原始) + if (vertices != null) { + for (int i = 0; i < vertices.length; i += 2) { + vertices[i] += dx; + vertices[i + 1] += dy; + } + } + if (originalVertices != null) { + for (int i = 0; i < originalVertices.length; i += 2) { + originalVertices[i] += dx; + originalVertices[i + 1] += dy; + } + } + + logger.debug("applyDeltaToMesh dx={}, dy={} applied to mesh", dx, dy); + } + + /** + * 新增:添加二级顶点(调用优化器确保半径分配合理) + */ + public void addSecondaryVertex(SecondaryVertex newV) { + if (secondaryVertices == null) return; + secondaryVertices.add(newV); + // 初始插入后用优化器处理与邻域的半径冲突 + RegionOptimizer.resolveForInsertedVertex(newV, secondaryVertices); + // 可选:立刻触发一次网格更新 + updateVerticesFromSecondaryVertices(); + } + + /** * 移除二级顶点 */ @@ -1437,25 +1184,284 @@ public class Mesh2D { * 根据二级顶点位置更新网格顶点(实现变形效果) */ public void updateVerticesFromSecondaryVertices() { - if (secondaryVertices.isEmpty() || vertices == null || originalVertices == null) { + if (secondaryVertices == null || secondaryVertices.isEmpty() || vertices == null || originalVertices == null) { return; } // 确保顶点数组长度匹配 if (originalVertices.length != vertices.length) { - logger.warn("原始顶点和当前顶点数组长度不匹配: {} != {}", - originalVertices.length, vertices.length); + logger.warn("原始顶点和当前顶点数组长度不匹配: {} != {}", originalVertices.length, vertices.length); return; } - // 使用重心坐标插值或双线性插值 - if (secondaryVertices.size() == 4) { - updateVerticesUsingBilinearInterpolationStable(); - } else { + // 如果控制点太少,直接使用反距离加权(兼容性) + if (secondaryVertices.size() < 3) { + updateVerticesUsingInverseDistanceWeighting(); + return; + } + + // 主要使用三角分配策略 + updateVerticesUsingTriangularPartition(); + } + + /** + * 使用“最近三点形成三角形 + 重心坐标映射”的方法更新顶点 + * 若三角形退化或点不在三角形内,则回退到最近点位移(nearest pin displacement)或最终回退到 IDW。 + */ + private void updateVerticesUsingTriangularPartition() { + try { + int secCount = secondaryVertices.size(); + + // 预取控制点的原始位置与当前位置(副本) + Vector2f[] secOrig = new Vector2f[secCount]; + Vector2f[] secCurr = new Vector2f[secCount]; + for (int i = 0; i < secCount; i++) { + secOrig[i] = secondaryVertices.get(i).getOriginalPosition(); // 原始局部坐标 + secCurr[i] = secondaryVertices.get(i).getPosition(); // 当前局部/世界坐标(视实现而定) + } + + for (int i = 0; i < originalVertices.length; i += 2) { + float origX = originalVertices[i]; + float origY = originalVertices[i + 1]; + + // 找到距离该点最近的三个控制点索引(优先考虑 controlRadius 的实现由 findNearestNIndices 决定) + int[] nearest = findNearestNIndices(origX, origY, 3, secOrig); + + // 如果未能找到 3 个点,回退到 IDW(基于位移 delta) + if (nearest == null || nearest.length < 3) { + Vector2f idw = computeIDWForPoint(origX, origY, secOrig, secCurr); + vertices[i] = idw.x; + vertices[i + 1] = idw.y; + continue; + } + + int ia = nearest[0], ib = nearest[1], ic = nearest[2]; + Vector2f A = secOrig[ia]; + Vector2f B = secOrig[ib]; + Vector2f C = secOrig[ic]; + + // 检测三角形是否退化(面积接近 0) + float area2 = Math.abs((B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y)); + if (area2 < 1e-6f) { + // 退化:回退到 IDW(基于位移 delta) + Vector2f idw = computeIDWForPoint(origX, origY, secOrig, secCurr); + vertices[i] = idw.x; + vertices[i + 1] = idw.y; + continue; + } + + // 点在三角形内部则使用重心坐标映射,但映射的是“位移 delta”,以保持整体位置不变 + if (pointInTriangle(origX, origY, A, B, C)) { + float[] bary = barycentricCoordinates(A, B, C, origX, origY); + + // 计算每个控制点的 delta(current - original) + Vector2f Acur = secCurr[ia]; + Vector2f Bcur = secCurr[ib]; + Vector2f Ccur = secCurr[ic]; + + float dAx = Acur.x - A.x; + float dAy = Acur.y - A.y; + float dBx = Bcur.x - B.x; + float dBy = Bcur.y - B.y; + float dCx = Ccur.x - C.x; + float dCy = Ccur.y - C.y; + + // 按重心系数混合 delta + float dx = bary[0] * dAx + bary[1] * dBx + bary[2] * dCx; + float dy = bary[0] * dAy + bary[1] * dBy + bary[2] * dCy; + + // 新位置 = 原始顶点位置 + 混合位移(不会把整个网格移动到原点) + vertices[i] = origX + dx; + vertices[i + 1] = origY + dy; + } else { + // 不在三角形内:回退到 IDW(基于位移 delta) + Vector2f idw = computeIDWForPoint(origX, origY, secOrig, secCurr); + vertices[i] = idw.x; + vertices[i + 1] = idw.y; + } + } + + logger.debug("应用三角分配变形(使用位移 delta 保持全局位置),使用了 {} 个控制点", secondaryVertices.size()); + + } catch (Exception e) { + logger.error("三角分配变形失败,回退到反距离加权", e); + // 出错回退 updateVerticesUsingInverseDistanceWeighting(); } } + /** + * 基于反距离加权(IDW)但对“位移 delta”加权计算结果,保证不会把整个网格搬到 (0,0)。 + * 返回最终的绝对坐标(orig + weighted_delta) + */ + private Vector2f computeIDWForPoint(float ox, float oy, Vector2f[] secOrig, Vector2f[] secCurr) { + final float power = 2.0f; + final float eps = 1e-5f; + + float nx = ox; + float ny = oy; + float weightSum = 0f; + + for (int k = 0; k < secOrig.length; k++) { + Vector2f sO = secOrig[k]; + Vector2f sC = secCurr[k]; + + float dx = ox - sO.x; + float dy = oy - sO.y; + float dist = (float) Math.sqrt(dx * dx + dy * dy); + + // 如果原始顶点非常靠近某个控制点的原始位置,直接使用该控制点的 delta(避免数值不稳定) + if (dist < eps) { + float deltaX = sC.x - sO.x; + float deltaY = sC.y - sO.y; + return new Vector2f(ox + deltaX, oy + deltaY); + } + + float w = 1.0f / (float) Math.pow(dist, power); + float deltaX = sC.x - sO.x; + float deltaY = sC.y - sO.y; + + nx += w * deltaX; + ny += w * deltaY; + weightSum += w; + } + + if (weightSum > 0f) { + nx = (ox * 1.0f + (nx - ox) / 1.0f * 1.0f); // 保持结构:ox + (sum(w*delta)/sum(w)) + // 计算正确的加权和:我们已经把 ox added multiple times, 修正如下: + // 实际上应为 ox + ( sum(w*delta) / sum(w) ) + // 为此重新计算 sum(w*deltaX) 与 sum(w*deltaY) + float sumWX = 0f; + float sumWY = 0f; + weightSum = 0f; + for (int k = 0; k < secOrig.length; k++) { + Vector2f sO = secOrig[k]; + Vector2f sC = secCurr[k]; + float dx = ox - sO.x; + float dy = oy - sO.y; + float dist = (float) Math.sqrt(dx * dx + dy * dy); + if (dist < eps) { + float deltaX = sC.x - sO.x; + float deltaY = sC.y - sO.y; + return new Vector2f(ox + deltaX, oy + deltaY); + } + float w = 1.0f / (float) Math.pow(dist, power); + float deltaX = sC.x - sO.x; + float deltaY = sC.y - sO.y; + sumWX += w * deltaX; + sumWY += w * deltaY; + weightSum += w; + } + if (weightSum > 0f) { + float dxFinal = sumWX / weightSum; + float dyFinal = sumWY / weightSum; + return new Vector2f(ox + dxFinal, oy + dyFinal); + } else { + return new Vector2f(ox, oy); + } + } else { + return new Vector2f(ox, oy); + } + } + + /** + * 在给定的控制点数组中,返回距离 (x,y) 最近的 N 个索引(按距离升序) + * 如果可用控制点少于 n,返回实际找到的索引数组。 + */ + private int[] findNearestNIndices(float x, float y, int n, Vector2f[] controlOrig) { + int secCount = controlOrig.length; + // 先收集所有在自己 controlRadius 内的点 + float[] dists = new float[secCount]; + for (int i = 0; i < secCount; i++) { + float dx = x - controlOrig[i].x; + float dy = y - controlOrig[i].y; + dists[i] = dx * dx + dy * dy; + } + + // 首先把满足 controlRadius 条件的点挑出来(并按距离排序) + java.util.List inRange = new java.util.ArrayList<>(); + java.util.List others = new java.util.ArrayList<>(); + for (int i = 0; i < secCount; i++) { + float r = secondaryVertices.get(i).getControlRadius(); + if (dists[i] <= r * r) inRange.add(i); + else others.add(i); + } + + // 排序辅助 + java.util.Comparator comp = (a, b) -> Float.compare(dists[a], dists[b]); + inRange.sort(comp); + others.sort(comp); + + java.util.List chosen = new java.util.ArrayList<>(); + for (int idx : inRange) { + if (chosen.size() >= n) break; + chosen.add(idx); + } + for (int idx : others) { + if (chosen.size() >= n) break; + chosen.add(idx); + } + + int[] result = new int[chosen.size()]; + for (int i = 0; i < chosen.size(); i++) result[i] = chosen.get(i); + return result; + } + + /** + * 检查点 (px,py) 是否位于由 A,B,C 三点形成的三角形内部(含边界) + * 使用重心 / 符号面积法 + */ + private boolean pointInTriangle(float px, float py, Vector2f A, Vector2f B, Vector2f C) { + float ax = A.x, ay = A.y; + float bx = B.x, by = B.y; + float cx = C.x, cy = C.y; + + float v0x = cx - ax, v0y = cy - ay; + float v1x = bx - ax, v1y = by - ay; + float v2x = px - ax, v2y = py - ay; + + float dot00 = v0x * v0x + v0y * v0y; + float dot01 = v0x * v1x + v0y * v1y; + float dot02 = v0x * v2x + v0y * v2y; + float dot11 = v1x * v1x + v1y * v1y; + float dot12 = v1x * v2x + v1y * v2y; + + float denom = (dot00 * dot11 - dot01 * dot01); + if (Math.abs(denom) < 1e-8f) return false; // 退化三角形 + + float invDenom = 1.0f / denom; + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // 在三角形内或边上时 u>=0, v>=0, u+v<=1 + return u >= -1e-6f && v >= -1e-6f && (u + v) <= 1.000001f; + } + + /** + * 计算点在三角形 ABC 的重心坐标(返回长度为3的数组 [wA, wB, wC]) + * 对退化情况不做保护,调用方应先检测面积。 + */ + private float[] barycentricCoordinates(Vector2f A, Vector2f B, Vector2f C, float px, float py) { + float x1 = A.x, y1 = A.y; + float x2 = B.x, y2 = B.y; + float x3 = C.x, y3 = C.y; + + float denom = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3); + if (Math.abs(denom) < 1e-8f) { + // 退化时返回把权重全部交给最近的顶点(Fallback) + float da = (px - x1) * (px - x1) + (py - y1) * (py - y1); + float db = (px - x2) * (px - x2) + (py - y2) * (py - y2); + float dc = (px - x3) * (px - x3) + (py - y3) * (py - y3); + if (da <= db && da <= dc) return new float[]{1f, 0f, 0f}; + if (db <= da && db <= dc) return new float[]{0f, 1f, 0f}; + return new float[]{0f, 0f, 1f}; + } + float w1 = ((y2 - y3) * (px - x3) + (x3 - x2) * (py - y3)) / denom; + float w2 = ((y3 - y1) * (px - x3) + (x1 - x3) * (py - y3)) / denom; + float w3 = 1.0f - w1 - w2; + return new float[]{w1, w2, w3}; + } + /** * 使用稳定的双线性插值更新顶点 */ @@ -1801,112 +1807,6 @@ public class Mesh2D { } } - // ==================== 二级顶点渲染 ==================== - - /** - * 绘制二级顶点 - */ - private void drawSecondaryVertices(Matrix3f modelMatrix) { - if (!showSecondaryVertices || secondaryVertices.isEmpty()) return; - - RenderSystem.pushState(); - try { - ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader"); - if (solidShader != null && solidShader.programId != 0) { - solidShader.use(); - - // 设置模型矩阵 - int modelLoc = solidShader.getUniformLocation("uModelMatrix"); - if (modelLoc != -1) { - RenderSystem.uniformMatrix3(modelLoc, modelMatrix); - } - } - - RenderSystem.enableBlend(); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - - Tesselator t = Tesselator.getInstance(); - BufferBuilder bb = t.getBuilder(); - - // 绘制所有二级顶点 - for (SecondaryVertex vertex : secondaryVertices) { - Vector2f position = vertex.getPosition(); - Vector4f color = vertex.isSelected() ? selectedSecondaryVertexColor : secondaryVertexColor; - - drawVertexPoint(bb, position.x, position.y, color, secondaryVertexSize); - - // 为选中的顶点绘制编号 - if (vertex.isSelected()) { - drawVertexId(bb, vertex.getId(), position.x, position.y); - } - } - - } finally { - RenderSystem.popState(); - } - } - - /** - * 绘制顶点标记点 - */ - private void drawVertexPoint(BufferBuilder bb, float x, float y, Vector4f color, float size) { - float halfSize = size / 2; - - // 绘制方形顶点标记 - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(color); - - bb.vertex(x - halfSize, y - halfSize, 0f, 0f); - bb.vertex(x + halfSize, y - halfSize, 0f, 0f); - bb.vertex(x + halfSize, y + halfSize, 0f, 0f); - - bb.vertex(x + halfSize, y + halfSize, 0f, 0f); - bb.vertex(x - halfSize, y + halfSize, 0f, 0f); - bb.vertex(x - halfSize, y - halfSize, 0f, 0f); - - bb.endImmediate(); - - // 绘制边框 - bb.begin(GL11.GL_LINE_LOOP, 4); - bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); - - bb.vertex(x - halfSize, y - halfSize, 0f, 0f); - bb.vertex(x + halfSize, y - halfSize, 0f, 0f); - bb.vertex(x + halfSize, y + halfSize, 0f, 0f); - bb.vertex(x - halfSize, y + halfSize, 0f, 0f); - - bb.endImmediate(); - } - - /** - * 绘制顶点ID编号 - */ - private void drawVertexId(BufferBuilder bb, int id, float x, float y) { - String idText = String.valueOf(id); - TextRenderer textRenderer = ModelRender.getTextRenderer(); - if (textRenderer != null) { - float textWidth = textRenderer.getTextWidth(idText); - float textX = x + secondaryVertexSize + 2.0f; - float textY = y - 4.0f; - - // 绘制文字背景 - bb.begin(GL11.GL_TRIANGLES, 6); - bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f)); - - bb.vertex(textX - 2, textY - 8, 0f, 0f); - bb.vertex(textX + textWidth + 2, textY - 8, 0f, 0f); - bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f); - - bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f); - bb.vertex(textX - 2, textY + 2, 0f, 0f); - bb.vertex(textX - 2, textY - 8, 0f, 0f); - - bb.endImmediate(); - - // 绘制文字 - ModelRender.renderText(idText, textX, textY, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); - } - } // ==================== 顶点操作 ==================== /** @@ -2647,17 +2547,7 @@ public class Mesh2D { } } - if (showLiquifyOverlay) { - drawLiquifyOverlay(shaderProgram, modelMatrix); - } - - if (showSecondaryVertices && !secondaryVertices.isEmpty()) { - drawSecondaryVertices(modelMatrix); - } - - if (showPuppetPins && !puppetPins.isEmpty()) { - drawPuppetPins(modelMatrix); - } + RanderToolsManager.getInstance().renderAllTools(modelMatrix, this); if (isSuspension && !selected) { RenderSystem.pushState(); diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/PuppetPin.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/PuppetPin.java index 3fe9430..af9957f 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/PuppetPin.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/PuppetPin.java @@ -11,6 +11,14 @@ import java.util.Map; public class PuppetPin { private static int NEXT_ID = 0; + // 衰减类型枚举 + public enum FalloffType { + LINEAR, // 线性衰减 + SMOOTH, // 平滑衰减 + SHARP, // 锐利衰减 + CONSTANT // 恒定衰减 + } + private int id; private final Vector2f position; private final Vector2f originalPosition; @@ -18,6 +26,7 @@ public class PuppetPin { private float influenceRadius = 100.0f; private boolean selected = false; private String name; + private FalloffType falloffType = FalloffType.SMOOTH; // 默认使用平滑衰减 // 权重贴图(顶点索引 -> 权重值) private final Map weightMap = new HashMap<>(); @@ -38,9 +47,24 @@ public class PuppetPin { return 0.0f; } - // 使用平滑的衰减函数 float normalizedDistance = distance / influenceRadius; - float weight = (float) (Math.cos(normalizedDistance * Math.PI) + 1) / 2.0f; + float weight = 0.0f; + + // 根据衰减类型计算权重 + switch (falloffType) { + case LINEAR: + weight = 1.0f - normalizedDistance; + break; + case SMOOTH: + weight = (float) (1.0f - Math.pow(normalizedDistance, 2)); + break; + case SHARP: + weight = (float) (1.0f - Math.pow(normalizedDistance, 0.5f)); + break; + case CONSTANT: + weight = 1.0f; + break; + } return weight; } @@ -59,6 +83,20 @@ public class PuppetPin { // ==================== 新增方法 ==================== + /** + * 获取衰减类型 + */ + public FalloffType getFalloffType() { + return falloffType; + } + + /** + * 设置衰减类型 + */ + public void setFalloffType(FalloffType falloffType) { + this.falloffType = falloffType; + } + /** * 设置原始位置 */ diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/RegionOptimizer.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/RegionOptimizer.java new file mode 100644 index 0000000..660bf61 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/RegionOptimizer.java @@ -0,0 +1,191 @@ +package com.chuangzhou.vivid2D.render.model.util; + +import org.joml.Vector2f; + +import java.util.List; + +/** + * RegionOptimizer + * - 处理当新点靠近已有点时的 controlRadius 重新分配(避免两个点控制区完全相同且重叠) + * - 提供针对特征区域(例如嘴巴、尾巴)的简单优化算法接口 + *

+ * 算法思想(简述): + * - 当新点插入或靠近已有点时,对两点及其邻域进行局部半径重分配,保证半径不相等且满足最小/最大约束。 + * - 对于特征(MOUTH/TAIL),使用基于位置的权重缩放半径(例如嘴巴中间半径较小以保证细节,边缘半径较大) + */ +public class RegionOptimizer { + + public enum FeatureType { MOUTH, TAIL, OTHER } + + // 新点插入时处理(调用 resolveNewAndNeighbor 或 resolveForInsertedVertex) + public static void resolveForInsertedVertex(SecondaryVertex newV, List all) { + // 与最近一个点进行冲突检测并调整 + SecondaryVertex nearest = findNearest(newV.getPosition().x, newV.getPosition().y, all, newV); + if (nearest != null) { + resolveNewAndNeighbor(newV, nearest, all); + } else { + // 无邻点,只需保证半径在范围内 + clampRadius(newV); + } + } + + /** + * 当新点 A 想进入 B 的控制区时:对 A、B 以及两者周围若干点做局部重分配 + * 目标:避免 A/B 的 controlRadius 完全相同或产生不可分配的覆盖(保证每个点都有独立控制区) + */ + public static void resolveNewAndNeighbor(SecondaryVertex A, SecondaryVertex B, List all) { + if (A == null || B == null) return; + + // 如果任一为 fixedRadius,则优先尊重 fixed,另一方调整 + if (A.isFixedRadius() && B.isFixedRadius()) { + // 都固定:不允许完全重合,若重合则微调 A 的半径少量 + if (Math.abs(A.getControlRadius() - B.getControlRadius()) < 1e-3f) { + if (!A.isFixedRadius()) { + A.setControlRadius(A.getControlRadius() * 0.95f + 0.1f); + } else if (!B.isFixedRadius()) { + B.setControlRadius(B.getControlRadius() * 0.95f + 0.1f); + } else { + // 两个都固定且相等,强制对 A 做微小扰动(尽量不破坏 fixed 标记) + A.setControlRadius(A.getControlRadius() + 0.5f); + } + } + return; + } + + // 否则对两者进行比例缩放:较远的一方保持或略增,靠近的一方减小 + float dist = A.getPosition().distance(B.getPosition()); + float sum = A.getControlRadius() + B.getControlRadius(); + // 如果重叠(距离 < sum),则调整半径 + if (dist < sum) { + // 按距离比重分配空间(保持最小阈值) + float minR = Math.min(Math.max(A.getMinControlRadius(), B.getMinControlRadius()), 4.0f); + + // 计算比例(避免完全相等) + float aPref = Math.max(minR, (A.getControlRadius() * (dist / (sum + 1e-6f))) * 0.9f); + float bPref = Math.max(minR, (B.getControlRadius() * (dist / (sum + 1e-6f))) * 0.9f); + + // 防止 aPref == bPref + if (Math.abs(aPref - bPref) < 1e-2f) { + aPref *= 0.92f; + bPref *= 1.08f; + } + + if (!A.isFixedRadius()) A.setControlRadius(aPref); + if (!B.isFixedRadius()) B.setControlRadius(bPref); + } else { + // 无重叠时,微调避免完全相等 + if (!A.isFixedRadius() && !B.isFixedRadius() && Math.abs(A.getControlRadius() - B.getControlRadius()) < 1e-3f) { + A.setControlRadius(A.getControlRadius() * 0.95f + 0.1f); + } + } + + // 可选:对二者邻域做平滑(简单缓和) + smoothNeighborhood(A, all, 2); + smoothNeighborhood(B, all, 2); + } + + /** + * 移动后调整邻域(简单的重分配与平滑) + */ + public static void adjustRegionsAfterMove(SecondaryVertex moved, List all) { + if (moved == null) return; + // 对周围一定范围内的点进行重平衡,防止刚好重叠或半径完全一致 + for (SecondaryVertex other : all) { + if (other == moved) continue; + float d = moved.getPosition().distance(other.getPosition()); + float influence = moved.getControlRadius() + other.getControlRadius(); + if (d < influence * 1.15f) { + // 近邻则 resolve pair + resolveNewAndNeighbor(moved, other, all); + } + } + } + + /** + * 对特征区域(如嘴巴/尾巴)进行优化:这里给出简单策略, + * 真实项目可替换为更复杂的曲线导向分配(例如沿曲线做非均匀采样、Laplacian 平滑等) + */ + public static void optimizeFeatureRegion(List featureVerts, FeatureType type) { + if (featureVerts == null || featureVerts.isEmpty()) return; + + // 计算质心 + Vector2f center = new Vector2f(0,0); + for (SecondaryVertex v : featureVerts) center.add(v.getPosition()); + center.div(featureVerts.size()); + + // 基于距离做半径缩放:靠近中心半径较小,远离中心半径较大(嘴巴中间精细) + float maxRadius = 0f; + for (SecondaryVertex v : featureVerts) maxRadius = Math.max(maxRadius, v.getControlRadius()); + + for (SecondaryVertex v : featureVerts) { + float d = v.getPosition().distance(center); + // 归一化距离 + float maxD = 1e-6f; + for (SecondaryVertex vv : featureVerts) maxD = Math.max(maxD, vv.getPosition().distance(center)); + float norm = (maxD > 1e-6f) ? d / maxD : 0f; + + if (type == FeatureType.MOUTH) { + // 嘴巴:中心小半径、边缘稍大 + float target = Math.max(v.getMinControlRadius(), maxRadius * (0.5f + 0.7f * norm)); + if (!v.isFixedRadius()) v.setControlRadius(target); + } else if (type == FeatureType.TAIL) { + // 尾巴:从基部到末端半径逐渐减小(假设 featureVerts 顺序已沿尾巴方向) + int idx = featureVerts.indexOf(v); + float t = (float) idx / (featureVerts.size() - 1.0f); + float target = Math.max(v.getMinControlRadius(), maxRadius * (1.0f - 0.7f * t)); + if (!v.isFixedRadius()) v.setControlRadius(target); + } else { + // 默认平滑:靠近中心略小 + float target = Math.max(v.getMinControlRadius(), maxRadius * (0.6f + 0.4f * norm)); + if (!v.isFixedRadius()) v.setControlRadius(target); + } + } + + // 最后做一次局部平滑避免跳变 + for (SecondaryVertex v : featureVerts) smoothNeighborhood(v, featureVerts, 1); + } + + // ----------------- 辅助方法 ----------------- + + private static SecondaryVertex findNearest(float x, float y, List all, SecondaryVertex exclude) { + SecondaryVertex best = null; + float bestD = Float.POSITIVE_INFINITY; + for (SecondaryVertex v : all) { + if (v == exclude) continue; + float dx = x - v.getPosition().x; + float dy = y - v.getPosition().y; + float d2 = dx*dx + dy*dy; + if (d2 < bestD) { + bestD = d2; + best = v; + } + } + return best; + } + + private static void clampRadius(SecondaryVertex v) { + if (v == null) return; + v.setControlRadius(v.getControlRadius()); // 利用 setter 做 clamp + } + + // 对邻域做简单平滑:把邻居半径平均到当前点附近(radiusNeighbors = k hop) + private static void smoothNeighborhood(SecondaryVertex v, List all, int radiusNeighbors) { + if (v == null || all == null) return; + // 取最近 couple 个(这里用固定 4 个邻居作为平滑范围) + java.util.List neighbors = new java.util.ArrayList<>(); + for (SecondaryVertex other : all) { + if (other == v) continue; + neighbors.add(other); + } + neighbors.sort((a,b) -> Float.compare(a.getPosition().distance(v.getPosition()), b.getPosition().distance(v.getPosition()))); + int k = Math.min(4, neighbors.size()); + float sum = v.getControlRadius(); + int cnt = 1; + for (int i = 0; i < k; i++) { + sum += neighbors.get(i).getControlRadius(); + cnt++; + } + float avg = sum / cnt; + if (!v.isFixedRadius()) v.setControlRadius( (v.getControlRadius() * 0.6f) + (avg * 0.4f) ); + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/SecondaryVertex.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/SecondaryVertex.java index 7088e6f..4262554 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/SecondaryVertex.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/SecondaryVertex.java @@ -5,7 +5,7 @@ import org.joml.Vector2f; import java.util.Objects; /** - * @author tzdwindows 7 + * SecondaryVertex 增加 pinned/locked 支持 */ public class SecondaryVertex { Vector2f position; @@ -15,6 +15,14 @@ public class SecondaryVertex { int id; static int nextId = 0; + // 新增状态 + boolean pinned = false; // 可以被……当作拖动整块) + boolean locked = false; // 锁定(不能移动) + private float controlRadius = 20.0f; // 控制区域半径(单位与你的坐标系一致),默认值可调整 + private float minControlRadius = 4.0f; // 最小允许半径 + private float maxControlRadius = 200.0f; // 最大允许半径 + private boolean fixedRadius = false; // 是否锁定半径(固定区域) + public SecondaryVertex(float x, float y, float u, float v) { this.position = new Vector2f(x, y); this.originalPosition = new Vector2f(x, y); @@ -83,6 +91,59 @@ public class SecondaryVertex { this.position.add(dx, dy); } + // 新增: pinned / locked + public boolean isPinned() { + return pinned; + } + + public void setPinned(boolean pinned) { + this.pinned = pinned; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + public float getControlRadius() { + return controlRadius; + } + + public void setControlRadius(float controlRadius) { + // 如果固定则不允许修改 + if (this.fixedRadius) return; + this.controlRadius = Math.max(minControlRadius, Math.min(maxControlRadius, controlRadius)); + } + + public float getMinControlRadius() { + return minControlRadius; + } + + public void setMinControlRadius(float minControlRadius) { + this.minControlRadius = Math.max(0f, minControlRadius); + if (this.controlRadius < this.minControlRadius) this.controlRadius = this.minControlRadius; + } + + public float getMaxControlRadius() { + return maxControlRadius; + } + + public void setMaxControlRadius(float maxControlRadius) { + this.maxControlRadius = Math.max(this.minControlRadius, maxControlRadius); + if (this.controlRadius > this.maxControlRadius) this.controlRadius = this.maxControlRadius; + } + + public boolean isFixedRadius() { + return fixedRadius; + } + + public void setFixedRadius(boolean fixedRadius) { + this.fixedRadius = fixedRadius; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -98,7 +159,7 @@ public class SecondaryVertex { @Override public String toString() { - return String.format("SecondaryVertex{id=%d, position=(%.2f, %.2f), uv=(%.2f, %.2f)}", - id, position.x, position.y, uv.x, uv.y); + return String.format("SecondaryVertex{id=%d, position=(%.2f, %.2f), uv=(%.2f, %.2f), pinned=%s, locked=%s}", + id, position.x, position.y, uv.x, uv.y, pinned, locked); } } diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/PuppetDeformationRander.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/PuppetDeformationRander.java new file mode 100644 index 0000000..056a149 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/PuppetDeformationRander.java @@ -0,0 +1,281 @@ +package com.chuangzhou.vivid2D.render.model.util.tools; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.TextRenderer; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.model.util.PuppetPin; +import com.chuangzhou.vivid2D.render.systems.RenderSystem; +import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder; +import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator; +import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement; +import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram; +import org.joml.Matrix3f; +import org.joml.Vector2f; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL11; + +import java.util.Map; + +public class PuppetDeformationRander extends RanderTools{ + + // 影响范围渲染颜色 + private final Vector4f influenceRangeColor = new Vector4f(0.3f, 0.3f, 1.0f, 0.3f); // 半透明蓝色 + private final Vector4f influenceBorderColor = new Vector4f(0.1f, 0.1f, 0.8f, 0.6f); // 边框颜色 + + @Override + public void init(Map algorithmEnabled) { + algorithmEnabled.put("showPuppetPins", false); + algorithmEnabled.put("showInfluenceRanges", true); // 控制是否显示影响范围 + } + + @Override + public boolean render(Matrix3f modelMatrix, Object renderContext) { + if (renderContext instanceof Mesh2D mesh2D) { + drawPuppetPins(mesh2D, modelMatrix); + return true; + } + return false; + } + + /** + * 绘制木偶控制点 + */ + private void drawPuppetPins(Mesh2D mesh2D, Matrix3f modelMatrix) { + if (!isAlgorithmEnabled("showPuppetPins") || mesh2D.getPuppetPins().isEmpty()) return; + + RenderSystem.pushState(); + try { + ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader"); + if (solidShader != null && solidShader.programId != 0) { + solidShader.use(); + + // 设置模型矩阵 + int modelLoc = solidShader.getUniformLocation("uModelMatrix"); + if (modelLoc != -1) { + RenderSystem.uniformMatrix3(modelLoc, modelMatrix); + } + } + + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + Tesselator t = Tesselator.getInstance(); + BufferBuilder bb = t.getBuilder(); + + // 绘制控制点影响范围 + if (isAlgorithmEnabled("showInfluenceRanges")) { + drawPuppetPinInfluenceRanges(mesh2D, bb); + } + + // 绘制控制点 + for (PuppetPin pin : mesh2D.getPuppetPins()) { + Vector2f position = pin.getPosition(); + Vector4f color = pin.isSelected() ? mesh2D.getSelectedPuppetPinColor() : mesh2D.getPuppetPinColor(); + + drawPuppetPin(bb, position.x, position.y, color, mesh2D.getPuppetPinSize(), pin.isSelected()); + + // 为选中的控制点绘制信息 + if (pin.isSelected()) { + drawPuppetPinInfo(mesh2D, bb, pin, position.x, position.y); + } + } + + } finally { + RenderSystem.popState(); + } + } + + /** + * 绘制控制点影响范围 - 显示实际的权重分布 + */ + private void drawPuppetPinInfluenceRanges(Mesh2D mesh2D, BufferBuilder bb) { + for (PuppetPin pin : mesh2D.getPuppetPins()) { + Vector2f position = pin.getPosition(); + float radius = pin.getInfluenceRadius(); + + // 获取控制点的权重映射 + Map weightMap = pin.getWeightMap(); + if (weightMap == null || weightMap.isEmpty()) continue; + + // 绘制影响范围的网格可视化 - 使用预计算的权重 + drawInfluenceGridFromWeightMap(mesh2D, bb, pin, position, radius, weightMap); + + // 绘制影响范围边界 + drawInfluenceBoundary(bb, position, radius); + } + } + + /** + * 根据预计算的权重映射绘制影响范围 + */ + private void drawInfluenceGridFromWeightMap(Mesh2D mesh2D, BufferBuilder bb, PuppetPin pin, + Vector2f position, float radius, Map weightMap) { + // 创建网格来可视化影响范围 + int gridSize = 12; // 减少网格细分以提高性能 + float cellSize = radius * 2 / gridSize; + + for (int i = 0; i < gridSize; i++) { + for (int j = 0; j < gridSize; j++) { + float x = position.x - radius + i * cellSize + cellSize / 2; + float y = position.y - radius + j * cellSize + cellSize / 2; + + // 计算该点到控制点的距离 + float dx = x - position.x; + float dy = y - position.y; + float distance = (float) Math.sqrt(dx * dx + dy * dy); + + if (distance <= radius) { + // 使用预计算的权重函数,确保与变形算法一致 + float weight = calculateWeight(distance, radius, pin.getFalloffType()); + + // 根据权重设置颜色强度 + Vector4f cellColor = new Vector4f( + influenceRangeColor.x, + influenceRangeColor.y, + influenceRangeColor.z, + influenceRangeColor.w * weight + ); + + // 绘制网格单元 + drawInfluenceCell(bb, x, y, cellSize * 0.8f, cellColor); + } + } + } + } + + /** + * 绘制影响范围边界 + */ + private void drawInfluenceBoundary(BufferBuilder bb, Vector2f position, float radius) { + // 绘制边界圆圈 + bb.begin(GL11.GL_LINE_LOOP, 32); + bb.setColor(influenceBorderColor); + + for (int i = 0; i < 32; i++) { + float angle = (float) (i * 2 * Math.PI / 32); + float x = position.x + (float) Math.cos(angle) * radius; + float y = position.y + (float) Math.sin(angle) * radius; + bb.vertex(x, y, 0f, 0f); + } + bb.endImmediate(); + } + + /** + * 绘制影响范围网格单元 + */ + private void drawInfluenceCell(BufferBuilder bb, float x, float y, float size, Vector4f color) { + float halfSize = size / 2; + + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(color); + + // 绘制小方块 + bb.vertex(x - halfSize, y - halfSize, 0f, 0f); + bb.vertex(x + halfSize, y - halfSize, 0f, 0f); + bb.vertex(x + halfSize, y + halfSize, 0f, 0f); + + bb.vertex(x + halfSize, y + halfSize, 0f, 0f); + bb.vertex(x - halfSize, y + halfSize, 0f, 0f); + bb.vertex(x - halfSize, y - halfSize, 0f, 0f); + + bb.endImmediate(); + } + + /** + * 计算权重(与木偶变形算法保持一致) + */ + private float calculateWeight(float distance, float radius, PuppetPin.FalloffType falloffType) { + if (distance >= radius) return 0.0f; + + float normalizedDistance = distance / radius; + + switch (falloffType) { + case LINEAR: + return 1.0f - normalizedDistance; + case SMOOTH: + return (float) (1.0f - Math.pow(normalizedDistance, 2)); + case SHARP: + return (float) (1.0f - Math.pow(normalizedDistance, 0.5f)); + case CONSTANT: + return 1.0f; + default: + return 1.0f - normalizedDistance; + } + } + + /** + * 绘制木偶控制点(更醒目的样式) + */ + private void drawPuppetPin(BufferBuilder bb, float x, float y, Vector4f color, float size, boolean selected) { + float halfSize = size / 2; + + if (selected) { + // 选中的控制点:带圆圈的十字 + bb.begin(GL11.GL_LINES, 4); + bb.setColor(color); + bb.vertex(x - halfSize, y, 0f, 0f); + bb.vertex(x + halfSize, y, 0f, 0f); + bb.vertex(x, y - halfSize, 0f, 0f); + bb.vertex(x, y + halfSize, 0f, 0f); + bb.endImmediate(); + + // 外圈圆圈 + bb.begin(GL11.GL_LINE_LOOP, 16); + bb.setColor(color); + float circleSize = size * 1.5f; + for (int i = 0; i < 16; i++) { + float angle = (float) (i * 2 * Math.PI / 16); + float cx = x + (float) Math.cos(angle) * circleSize; + float cy = y + (float) Math.sin(angle) * circleSize; + bb.vertex(cx, cy, 0f, 0f); + } + bb.endImmediate(); + } else { + // 普通控制点:实心圆圈 + bb.begin(GL11.GL_TRIANGLE_FAN, 16); + bb.setColor(color); + bb.vertex(x, y, 0f, 0f); // 中心点 + for (int i = 0; i <= 16; i++) { + float angle = (float) (i * 2 * Math.PI / 16); + float cx = x + (float) Math.cos(angle) * halfSize; + float cy = y + (float) Math.sin(angle) * halfSize; + bb.vertex(cx, cy, 0f, 0f); + } + bb.endImmediate(); + + // 边框 + bb.begin(GL11.GL_LINE_LOOP, 16); + bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); + for (int i = 0; i < 16; i++) { + float angle = (float) (i * 2 * Math.PI / 16); + float cx = x + (float) Math.cos(angle) * halfSize; + float cy = y + (float) Math.sin(angle) * halfSize; + bb.vertex(cx, cy, 0f, 0f); + } + bb.endImmediate(); + } + } + + /** + * 绘制控制点信息 + */ + private void drawPuppetPinInfo(Mesh2D mesh2D, BufferBuilder bb, PuppetPin pin, float x, float y) { + String infoText = pin.getName() + " (R:" + (int) pin.getInfluenceRadius() + ")"; + TextRenderer textRenderer = ModelRender.getTextRenderer(); + if (textRenderer != null) { + float textWidth = textRenderer.getTextWidth(infoText); + float textX = x + mesh2D.getPuppetPinSize() + 5.0f; + float textY = y - 6.0f; + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f)); + bb.vertex(textX - 3, textY - 10, 0f, 0f); + bb.vertex(textX + textWidth + 3, textY - 10, 0f, 0f); + bb.vertex(textX + textWidth + 3, textY + 4, 0f, 0f); + bb.vertex(textX + textWidth + 3, textY + 4, 0f, 0f); + bb.vertex(textX - 3, textY + 4, 0f, 0f); + bb.vertex(textX - 3, textY - 10, 0f, 0f); + bb.endImmediate(); + ModelRender.renderText(infoText, textX, textY, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/VertexDeformationRander.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/VertexDeformationRander.java new file mode 100644 index 0000000..922f758 --- /dev/null +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/tools/VertexDeformationRander.java @@ -0,0 +1,432 @@ +package com.chuangzhou.vivid2D.render.model.util.tools; + +import com.chuangzhou.vivid2D.render.ModelRender; +import com.chuangzhou.vivid2D.render.TextRenderer; +import com.chuangzhou.vivid2D.render.model.util.Mesh2D; +import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex; +import com.chuangzhou.vivid2D.render.systems.RenderSystem; +import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder; +import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator; +import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement; +import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram; +import org.joml.Matrix3f; +import org.joml.Vector2f; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL11; + +import java.util.Map; + +/** + * 改进:使二级顶点渲染更现代化(圆形渐变点、阴影、高光),并在选中时显示影响范围(半透明环) + * 目标风格:类似 Live2D 编辑器里点和影响范围的视觉呈现 + */ +public class VertexDeformationRander extends RanderTools { + @Override + public void init(Map algorithmEnabled) { + algorithmEnabled.put("showSecondaryVertices", false); + // 可选项:是否显示影响范围 + algorithmEnabled.put("showSecondaryVertexInfluence", true); + } + + @Override + public boolean render(Matrix3f modelMatrix, Object renderContext) { + if (renderContext instanceof Mesh2D mesh2D){ + drawSecondaryVertices(mesh2D, modelMatrix); + return true; + } + return false; + } + + /** + * 绘制二级顶点(现代化样式) + */ + private void drawSecondaryVertices(Mesh2D mesh2D, Matrix3f modelMatrix) { + if (!isAlgorithmEnabled("showSecondaryVertices") || mesh2D.getSecondaryVertices().isEmpty()) return; + + RenderSystem.pushState(); + try { + ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader"); + if (solidShader != null && solidShader.programId != 0) { + solidShader.use(); + + // 设置模型矩阵(如果 shader 支持) + int modelLoc = solidShader.getUniformLocation("uModelMatrix"); + if (modelLoc != -1) { + RenderSystem.uniformMatrix3(modelLoc, modelMatrix); + } + } + + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + // 启用线段平滑(如果驱动支持) + GL11.glEnable(GL11.GL_LINE_SMOOTH); + GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST); + + Tesselator t = Tesselator.getInstance(); + BufferBuilder bb = t.getBuilder(); + + // 1) 先绘制细线连接所有控制点(在点之后绘制也可以,这里画在下层) + drawConnectionLines(bb, mesh2D); + + // 2) 为每个点绘制局部三角分配(用两最近邻构成三角形)并填充半透明三角形,表示该点的控制区域示意 + for (SecondaryVertex vertex : mesh2D.getSecondaryVertices()) { + drawLocalTriangle(bb, vertex, mesh2D); + } + + // 3) 绘制所有二级顶点(点本体 + 高光 + 边框 + pin/lock 标识 + 编号) + for (SecondaryVertex vertex : mesh2D.getSecondaryVertices()) { + Vector2f position = vertex.getPosition(); + Vector4f baseColor = vertex.isSelected() ? mesh2D.selectedSecondaryVertexColor : mesh2D.secondaryVertexColor; + float size = mesh2D.secondaryVertexSize; + + // 阴影(轻微偏移) + drawCircleSolid(bb, position.x + 2f, position.y - 2f, size * 0.8f, new Vector4f(0f, 0f, 0f, 0.22f), 20); + + // 如果开启显示影响范围且顶点被选中,则绘制半透明环表示影响范围(使用 controlRadius) + if (vertex.isSelected() && isAlgorithmEnabled("showSecondaryVertexInfluence")) { + float influenceRadius = vertex.getControlRadius(); // 使用 SecondaryVertex 的 controlRadius + drawInfluenceRing(bb, position.x, position.y, influenceRadius, baseColor); + } + + // 圆形渐变主点(中心较亮、边缘柔化) + Vector4f centerCol = new Vector4f(baseColor.x, baseColor.y, baseColor.z, Math.min(1.0f, baseColor.w + 0.15f)); + Vector4f outerCol = new Vector4f(baseColor.x, baseColor.y, baseColor.z, baseColor.w * 0.9f); + drawCircleGradient(bb, position.x, position.y, size * 0.9f, centerCol, outerCol, 28); + + // 内部高光(小白点) + drawCircleSolid(bb, position.x - size * 0.12f, position.y + size * 0.12f, size * 0.22f, + new Vector4f(1f, 1f, 1f, 0.75f), 12); + + // 边框(细) + drawCircleOutline(bb, position.x, position.y, size * 0.95f, new Vector4f(1f, 1f, 1f, 0.9f), 28); + + // 绘制 pin / lock 图标(在点旁边) + drawPinLockIcon(bb, vertex, position.x, position.y, size); + + Vector2f preview = mesh2D.getPreviewPoint(); + if (preview != null) { + // 使用 mesh2D 提供的预测方法(临时点半径使用默认 secondaryVertexSize*2) + float[] predicted = mesh2D.predictVerticesWithTemporarySecondary(preview, mesh2D.secondaryVertexSize * 3.0f); + if (predicted != null) { + drawPredictedOutline(bb, predicted, mesh2D); + } + } + + // 为选中的顶点绘制编号(更多现代化:带阴影的半透明小标签) + if (vertex.isSelected()) { + drawVertexId(mesh2D, bb, vertex.getId(), position.x, position.y, size); + } + } + + } finally { + // 恢复状态 + GL11.glDisable(GL11.GL_LINE_SMOOTH); + RenderSystem.popState(); + } + } + + private void drawPredictedOutline(BufferBuilder bb, float[] predictedVertices, Mesh2D mesh2D) { + if (predictedVertices == null || predictedVertices.length < 4) return; + + // 1) 绘制网格轮廓(按顶点顺序连线)——半透明橙色 + bb.begin(GL11.GL_LINE_LOOP, predictedVertices.length / 2); + bb.setColor(new Vector4f(0.95f, 0.6f, 0.15f, 0.28f)); + for (int i = 0; i < predictedVertices.length; i += 2) { + bb.vertex(predictedVertices[i], predictedVertices[i + 1], 0f, 0f); + } + bb.endImmediate(); + + // 2) 绘制细线网(每隔若干顶点连线,提升可读性) + int step = Math.max(1, (predictedVertices.length / 2) / 40); // 控制线密度 + for (int i = 0; i < predictedVertices.length; i += 2 * step) { + int j = (i + 2 * step) % predictedVertices.length; + bb.begin(GL11.GL_LINES, 2); + bb.setColor(new Vector4f(0.95f, 0.6f, 0.15f, 0.12f)); + bb.vertex(predictedVertices[i], predictedVertices[i + 1], 0f, 0f); + bb.vertex(predictedVertices[j], predictedVertices[j + 1], 0f, 0f); + bb.endImmediate(); + } + + // 3) 绘制预测顶点的小圆点(半透明,便于与真实点区分) + float psize = mesh2D.secondaryVertexSize * 0.6f; + for (int i = 0; i < predictedVertices.length; i += 2) { + drawCircleSolid(bb, predictedVertices[i], predictedVertices[i + 1], psize, new Vector4f(0.95f, 0.6f, 0.15f, 0.9f), 10); + drawCircleOutline(bb, predictedVertices[i], predictedVertices[i + 1], psize * 0.9f, new Vector4f(1f,1f,1f,0.85f), 10); + } + } + + /** + * 绘制细线连接控制点(按 secondaryVertices 列表顺序连接,线微透明) + */ + private void drawConnectionLines(BufferBuilder bb, Mesh2D mesh2D) { + java.util.List verts = mesh2D.getSecondaryVertices(); + if (verts.size() < 2) return; + + // 细线:连接顺序(通常用于可视化控制点序列) + GL11.glLineWidth(1.0f); + bb.begin(GL11.GL_LINE_STRIP, verts.size()); + bb.setColor(new Vector4f(1f, 1f, 1f, 0.12f)); + for (SecondaryVertex v : verts) { + Vector2f p = v.getPosition(); + bb.vertex(p.x, p.y, 0f, 0f); + } + bb.endImmediate(); + + // 另外绘制每对近邻间的微型链接(更明显的细虚线效果:用短段组合实现) + for (int i = 0; i < verts.size(); i++) { + SecondaryVertex a = verts.get(i); + SecondaryVertex b = verts.get((i + 1) % verts.size()); + drawDashedLine(bb, a.getPosition().x, a.getPosition().y, b.getPosition().x, b.getPosition().y, 6, 3, new Vector4f(1f,1f,1f,0.06f)); + } + } + + /** + * 绘制点的局部三角形(用两最近邻构成)并填充半透明,用于直观展示三角分配(非严格 Delaunay) + */ + private void drawLocalTriangle(BufferBuilder bb, SecondaryVertex v, Mesh2D mesh2D) { + java.util.List verts = mesh2D.getSecondaryVertices(); + if (verts.size() < 3) return; + + // 找两个最近邻 + SecondaryVertex n1 = null, n2 = null; + float best1 = Float.POSITIVE_INFINITY, best2 = Float.POSITIVE_INFINITY; + Vector2f pv = v.getPosition(); + for (SecondaryVertex other : verts) { + if (other == v) continue; + float d2 = pv.distanceSquared(other.getPosition()); + if (d2 < best1) { best2 = best1; n2 = n1; best1 = d2; n1 = other; } + else if (d2 < best2) { best2 = d2; n2 = other; } + } + if (n1 == null || n2 == null) return; + + // 半透明填充三角形(颜色基于 v 的颜色且带 alpha) + Vector4f triFill = new Vector4f(0.9f, 0.6f, 0.2f, 0.06f); // 示意色,可按需替换 + bb.begin(GL11.GL_TRIANGLES, 3); + bb.setColor(triFill); + bb.vertex(pv.x, pv.y, 0f, 0f); + bb.vertex(n1.getPosition().x, n1.getPosition().y, 0f, 0f); + bb.vertex(n2.getPosition().x, n2.getPosition().y, 0f, 0f); + bb.endImmediate(); + + // 三角形边框(细线,颜色取 controlRadius 是否固定的提示) + Vector4f edgeCol = v.isFixedRadius() ? new Vector4f(0.9f,0.4f,0.2f,0.9f) : new Vector4f(1f,1f,1f,0.12f); + bb.begin(GL11.GL_LINE_LOOP, 3); + bb.setColor(edgeCol); + bb.vertex(pv.x, pv.y, 0f, 0f); + bb.vertex(n1.getPosition().x, n1.getPosition().y, 0f, 0f); + bb.vertex(n2.getPosition().x, n2.getPosition().y, 0f, 0f); + bb.endImmediate(); + } + + /** + * 绘制虚线(通过分段短线模拟) + * segmentLen: 实线段长度, gapLen: 间隔长度 + */ + private void drawDashedLine(BufferBuilder bb, float x1, float y1, float x2, float y2, float segmentLen, float gapLen, Vector4f color) { + float dx = x2 - x1; + float dy = y2 - y1; + float total = (float)Math.sqrt(dx*dx + dy*dy); + if (total < 1e-4f) return; + float nx = dx / total; + float ny = dy / total; + + float pos = 0f; + while (pos < total) { + float segStart = pos; + float segEnd = Math.min(total, pos + segmentLen); + if (segEnd > segStart) { + float sx = x1 + nx * segStart; + float sy = y1 + ny * segStart; + float ex = x1 + nx * segEnd; + float ey = y1 + ny * segEnd; + bb.begin(GL11.GL_LINES, 2); + bb.setColor(color); + bb.vertex(sx, sy, 0f, 0f); + bb.vertex(ex, ey, 0f, 0f); + bb.endImmediate(); + } + pos += segmentLen + gapLen; + } + } + + /** + * 在点旁边绘制 pin / lock 小图标(用简单几何表示) + */ + private void drawPinLockIcon(BufferBuilder bb, SecondaryVertex v, float px, float py, float size) { + float iconSize = size * 0.9f; + float ix = px + size * 0.9f; + float iy = py + size * 0.2f; + + if (v.isPinned()) { + // 绘制一个小“钉子”样式(矩形竖条 + 圆头) + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(new Vector4f(0.95f, 0.75f, 0.2f, 0.95f)); + // 矩形竖条 + bb.vertex(ix - iconSize*0.12f, iy - iconSize*0.3f, 0f, 0f); + bb.vertex(ix + iconSize*0.12f, iy - iconSize*0.3f, 0f, 0f); + bb.vertex(ix + iconSize*0.12f, iy + iconSize*0.15f, 0f, 0f); + + bb.vertex(ix + iconSize*0.12f, iy + iconSize*0.15f, 0f, 0f); + bb.vertex(ix - iconSize*0.12f, iy + iconSize*0.15f, 0f, 0f); + bb.vertex(ix - iconSize*0.12f, iy - iconSize*0.3f, 0f, 0f); + bb.endImmediate(); + + // 圆头 + drawCircleSolid(bb, ix, iy + iconSize*0.25f, iconSize*0.18f, new Vector4f(1f,1f,1f,0.9f), 12); + } + + if (v.isLocked()) { + // 绘制一个小“锁”样式(圆角矩形 + 环) + float lx = ix + iconSize * 0.6f; + float ly = iy; + float w = iconSize * 0.8f; + float h = iconSize * 0.6f; + // 背景 + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(new Vector4f(0.16f,0.16f,0.16f,0.95f)); + bb.vertex(lx - w/2, ly - h/2, 0f, 0f); + bb.vertex(lx + w/2, ly - h/2, 0f, 0f); + bb.vertex(lx + w/2, ly + h/2, 0f, 0f); + + bb.vertex(lx + w/2, ly + h/2, 0f, 0f); + bb.vertex(lx - w/2, ly + h/2, 0f, 0f); + bb.vertex(lx - w/2, ly - h/2, 0f, 0f); + bb.endImmediate(); + + // 锁环(用半圆表现) + drawCircleGradient(bb, lx, ly - h*0.15f, w*0.35f, new Vector4f(1f,1f,1f,0.95f), new Vector4f(1f,1f,1f,0.6f), 10); + } + } + + /** + * 绘制实心圆(单色) + */ + private void drawCircleSolid(BufferBuilder bb, float cx, float cy, float radius, Vector4f color, int segments) { + if (radius <= 0f) return; + segments = Math.max(6, segments); + bb.begin(GL11.GL_TRIANGLE_FAN, segments + 2); + // 中心 + bb.setColor(color); + bb.vertex(cx, cy, 0f, 0f); + // 外环 + for (int i = 0; i <= segments; i++) { + double ang = 2.0 * Math.PI * i / segments; + float x = cx + (float) (Math.cos(ang) * radius); + float y = cy + (float) (Math.sin(ang) * radius); + bb.setColor(color); + bb.vertex(x, y, 0f, 0f); + } + bb.endImmediate(); + } + + /** + * 绘制带渐变的圆(中心颜色到边缘颜色) + */ + private void drawCircleGradient(BufferBuilder bb, float cx, float cy, float radius, Vector4f centerColor, Vector4f outerColor, int segments) { + if (radius <= 0f) return; + segments = Math.max(8, segments); + bb.begin(GL11.GL_TRIANGLE_FAN, segments + 2); + // 中心点使用中心颜色 + bb.setColor(centerColor); + bb.vertex(cx, cy, 0f, 0f); + + // 外环每个顶点使用 outerColor(可以按需对每个顶点略微调整颜色以获得更平滑的效果) + for (int i = 0; i <= segments; i++) { + double ang = 2.0 * Math.PI * i / segments; + float x = cx + (float) (Math.cos(ang) * radius); + float y = cy + (float) (Math.sin(ang) * radius); + bb.setColor(outerColor); + bb.vertex(x, y, 0f, 0f); + } + bb.endImmediate(); + } + + /** + * 绘制圆形边框(线框) + */ + private void drawCircleOutline(BufferBuilder bb, float cx, float cy, float radius, Vector4f color, int segments) { + if (radius <= 0f) return; + segments = Math.max(8, segments); + bb.begin(GL11.GL_LINE_LOOP, segments); + bb.setColor(color); + for (int i = 0; i < segments; i++) { + double ang = 2.0 * Math.PI * i / segments; + float x = cx + (float) (Math.cos(ang) * radius); + float y = cy + (float) (Math.sin(ang) * radius); + bb.vertex(x, y, 0f, 0f); + } + bb.endImmediate(); + } + + /** + * 绘制影响范围(半透明填充 + 边缘渐变) + */ + private void drawInfluenceRing(BufferBuilder bb, float cx, float cy, float radius, Vector4f baseColor) { + if (radius <= 0f) return; + // 内外颜色,外部更透明 + Vector4f inner = new Vector4f(baseColor.x, baseColor.y, baseColor.z, 0.12f); + Vector4f outer = new Vector4f(baseColor.x, baseColor.y, baseColor.z, 0.02f); + // 大致用两个同心渐变圆叠加表现柔和的影响范围 + drawCircleGradient(bb, cx, cy, radius, inner, outer, 48); + // 用一圈更明显的边界帮助辨识范围(细) + drawCircleOutline(bb, cx, cy, radius, new Vector4f(baseColor.x, baseColor.y, baseColor.z, 0.28f), 64); + } + + /** + * 绘制顶点ID编号(更现代的标签:阴影 + 半透明背景 + 白色文字) + */ + private void drawVertexId(Mesh2D mesh2D, BufferBuilder bb, int id, float x, float y, float size) { + String idText = String.valueOf(id); + TextRenderer textRenderer = ModelRender.getTextRenderer(); + if (textRenderer != null) { + float textWidth = textRenderer.getTextWidth(idText); + // 标签位置:点的右上方 + float textX = x + size + 6.0f; + float textY = y - size * 0.2f; + + // 阴影(矩形偏移) + float padX = 6f; + float padY = 4f; + float left = textX - padX; + float right = textX + textWidth + padX; + float top = textY - 12f - padY; + float bottom = textY + 4f + padY; + + // 阴影背景(偏移) + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(new Vector4f(0f, 0f, 0f, 0.25f)); + float sx = 2f, sy = -2f; + bb.vertex(left + sx, top + sy, 0f, 0f); + bb.vertex(right + sx, top + sy, 0f, 0f); + bb.vertex(right + sx, bottom + sy, 0f, 0f); + bb.vertex(right + sx, bottom + sy, 0f, 0f); + bb.vertex(left + sx, bottom + sy, 0f, 0f); + bb.vertex(left + sx, top + sy, 0f, 0f); + bb.endImmediate(); + + // 背景(半透明) + bb.begin(GL11.GL_TRIANGLES, 6); + bb.setColor(new Vector4f(0.06f, 0.06f, 0.06f, 0.88f)); + bb.vertex(left, top, 0f, 0f); + bb.vertex(right, top, 0f, 0f); + bb.vertex(right, bottom, 0f, 0f); + bb.vertex(right, bottom, 0f, 0f); + bb.vertex(left, bottom, 0f, 0f); + bb.vertex(left, top, 0f, 0f); + bb.endImmediate(); + + // 边框 + bb.begin(GL11.GL_LINE_LOOP, 4); + bb.setColor(new Vector4f(1f, 1f, 1f, 0.1f)); + bb.vertex(left, top, 0f, 0f); + bb.vertex(right, top, 0f, 0f); + bb.vertex(right, bottom, 0f, 0f); + bb.vertex(left, bottom, 0f, 0f); + bb.endImmediate(); + + // 文字(白色) + ModelRender.renderText(idText, textX, textY, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); + } + } +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/systems/RenderSystem.java b/src/main/java/com/chuangzhou/vivid2D/render/systems/RenderSystem.java index 0470811..a1aed98 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/systems/RenderSystem.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/systems/RenderSystem.java @@ -127,60 +127,107 @@ public final class RenderSystem { private float[] clearColor; private int[] viewport; + // 默认构造:尝试安全读取 GL 状态;如果失败则使用默认值(不抛异常) public RenderState() { setDefaults(); + // 使用 MemoryStack 能更安全地分配临时缓冲并自动释放 + try (org.lwjgl.system.MemoryStack stack = org.lwjgl.system.MemoryStack.stackPush()) { - try { - this.currentProgram = getCurrentProgram(); - this.blendEnabled = GL11.glIsEnabled(GL11.GL_BLEND); - this.depthTestEnabled = GL11.glIsEnabled(GL11.GL_DEPTH_TEST); - - java.nio.IntBuffer blendFunc = org.lwjgl.system.MemoryUtil.memAllocInt(2); + // current program try { - GL11.glGetIntegerv(GL11.GL_BLEND_SRC, blendFunc); - this.blendSrcFactor = blendFunc.get(0); - GL11.glGetIntegerv(GL11.GL_BLEND_DST, blendFunc); - this.blendDstFactor = blendFunc.get(0); - } finally { - org.lwjgl.system.MemoryUtil.memFree(blendFunc); + int prog = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM); + if (prog >= 0) this.currentProgram = prog; + } catch (Throwable ex) { + logger.debug("Could not read GL_CURRENT_PROGRAM: {}", ex.getMessage()); } - java.nio.IntBuffer intBuf = org.lwjgl.system.MemoryUtil.memAllocInt(1); + // blend / depth flags try { - GL11.glGetIntegerv(GL13.GL_ACTIVE_TEXTURE, intBuf); - this.activeTexture = intBuf.get(0); - - GL11.glGetIntegerv(GL11.GL_TEXTURE_BINDING_2D, intBuf); - this.boundTexture = intBuf.get(0); - } finally { - org.lwjgl.system.MemoryUtil.memFree(intBuf); + this.blendEnabled = GL11.glIsEnabled(GL11.GL_BLEND); + } catch (Throwable ex) { + logger.debug("Could not read GL_BLEND enabled: {}", ex.getMessage()); + } + try { + this.depthTestEnabled = GL11.glIsEnabled(GL11.GL_DEPTH_TEST); + } catch (Throwable ex) { + logger.debug("Could not read GL_DEPTH_TEST enabled: {}", ex.getMessage()); } - java.nio.FloatBuffer floatBuf = org.lwjgl.system.MemoryUtil.memAllocFloat(4); + // blend func (src/dst) — 分开询问并保护 try { - GL11.glGetFloatv(GL11.GL_COLOR_CLEAR_VALUE, floatBuf); - this.clearColor = new float[]{ - floatBuf.get(0), floatBuf.get(1), - floatBuf.get(2), floatBuf.get(3) - }; - } finally { - org.lwjgl.system.MemoryUtil.memFree(floatBuf); + java.nio.IntBuffer buf = stack.mallocInt(1); + GL11.glGetIntegerv(GL14.GL_BLEND_SRC_ALPHA, buf); // 尝试安全常量 + int src = buf.get(0); + if (isValidBlendFunc(src)) this.blendSrcFactor = src; + } catch (Throwable ex) { + // 退回到原有常量查询(兼容旧驱动),但都包裹在 try/catch + try { + java.nio.IntBuffer buf2 = stack.mallocInt(1); + GL11.glGetIntegerv(GL11.GL_BLEND_SRC, buf2); + int s = buf2.get(0); + if (isValidBlendFunc(s)) this.blendSrcFactor = s; + } catch (Throwable ex2) { + logger.debug("Could not read blend src: {}, {}", ex.getMessage(), ex2.getMessage()); + } } - java.nio.IntBuffer viewportBuf = org.lwjgl.system.MemoryUtil.memAllocInt(4); try { - GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewportBuf); - this.viewport = new int[]{ - viewportBuf.get(0), viewportBuf.get(1), - viewportBuf.get(2), viewportBuf.get(3) - }; - } finally { - org.lwjgl.system.MemoryUtil.memFree(viewportBuf); + java.nio.IntBuffer buf = stack.mallocInt(1); + GL11.glGetIntegerv(GL11.GL_BLEND_DST, buf); + int dst = buf.get(0); + if (isValidBlendFunc(dst)) this.blendDstFactor = dst; + } catch (Throwable ex) { + logger.debug("Could not read blend dst: {}", ex.getMessage()); } - } catch (Exception e) { - logger.warn("Failed to get render state, using defaults: {}", e.getMessage()); - // 如果出现异常,我们使用默认值(已经在setDefaults中设置,所以不需要再次设置) - } + + // active texture & bound texture —— 使用安全范围检查 + try { + java.nio.IntBuffer buf = stack.mallocInt(1); + GL11.glGetIntegerv(GL13.GL_ACTIVE_TEXTURE, buf); + int at = buf.get(0); + if (at >= GL13.GL_TEXTURE0 && at <= GL13.GL_TEXTURE31) { + this.activeTexture = at; + } else { + // 保持默认 + } + + GL11.glGetIntegerv(GL11.GL_TEXTURE_BINDING_2D, buf); + int bt = buf.get(0); + if (bt >= 0) this.boundTexture = bt; + } catch (Throwable ex) { + logger.debug("Could not read texture state: {}", ex.getMessage()); + } + + // clear color + try { + java.nio.FloatBuffer fbuf = stack.mallocFloat(4); + GL11.glGetFloatv(GL11.GL_COLOR_CLEAR_VALUE, fbuf); + this.clearColor = new float[]{fbuf.get(0), fbuf.get(1), fbuf.get(2), fbuf.get(3)}; + } catch (Throwable ex) { + logger.debug("Could not read clear color: {}", ex.getMessage()); + } + + // viewport + try { + java.nio.IntBuffer vbuf = stack.mallocInt(4); + GL11.glGetIntegerv(GL11.GL_VIEWPORT, vbuf); + int vx = vbuf.get(0), vy = vbuf.get(1), vw = vbuf.get(2), vh = vbuf.get(3); + // 做基本合法性检查:宽高 > 0 + if (vw > 0 && vh > 0) { + this.viewport = new int[]{vx, vy, vw, vh}; + } + } catch (Throwable ex) { + logger.debug("Could not read viewport: {}", ex.getMessage()); + } + } // MemoryStack 自动释放 + + // 再次清理任何遗留 GL error,确保 pushState 后渲染不被污染 + while (GL11.glGetError() != GL11.GL_NO_ERROR) { /* clear */ } + } + + // 新增:fallback 构造器,仅创建默认值(在捕获异常时使用) + public RenderState(boolean fallbackDefaults) { + setDefaults(); } private void setDefaults() { @@ -192,14 +239,19 @@ public final class RenderSystem { this.activeTexture = GL13.GL_TEXTURE0; this.boundTexture = 0; this.clearColor = new float[]{0.0f, 0.0f, 0.0f, 1.0f}; - this.viewport = new int[]{0, 0, viewportWidth, viewportHeight}; + // 尝试从系统获取视口默认:若没有则使用 stored viewportWidth/height(确保为正) + int vw = Math.max(1, viewportWidth); + int vh = Math.max(1, viewportHeight); + this.viewport = new int[]{0, 0, vw, vh}; } public void restore() { try { - // 恢复视口 + // 恢复视口(做范围与合法性校验) if (viewport != null && viewport.length == 4) { - GL11.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + int w = Math.max(1, Math.min(viewport[2], 65535)); + int h = Math.max(1, Math.min(viewport[3], 65535)); + GL11.glViewport(viewport[0], viewport[1], w, h); } // 恢复清除颜色 @@ -208,52 +260,44 @@ public final class RenderSystem { } // 恢复着色器程序 - if (GL20.glIsProgram(currentProgram)) { - GL20.glUseProgram(currentProgram); - } else { + try { + if (GL20.glIsProgram(currentProgram)) { + GL20.glUseProgram(currentProgram); + } else { + GL20.glUseProgram(0); + } + } catch (Throwable ex) { GL20.glUseProgram(0); } - // 恢复纹理状态 - 使用更安全的方式 + // 恢复纹理状态 - 检查 activeTexture 合法范围 if (activeTexture >= GL13.GL_TEXTURE0 && activeTexture <= GL13.GL_TEXTURE31) { GL13.glActiveTexture(activeTexture); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, boundTexture); } else { - // 使用默认纹理单元 GL13.glActiveTexture(GL13.GL_TEXTURE0); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, boundTexture); } + GL11.glBindTexture(GL11.GL_TEXTURE_2D, Math.max(0, boundTexture)); - // 恢复混合状态 - if (blendEnabled) { - GL11.glEnable(GL11.GL_BLEND); - } else { - GL11.glDisable(GL11.GL_BLEND); - } + // 恢复混合 + if (blendEnabled) GL11.glEnable(GL11.GL_BLEND); else GL11.glDisable(GL11.GL_BLEND); - // 使用安全的混合函数值 if (isValidBlendFunc(blendSrcFactor) && isValidBlendFunc(blendDstFactor)) { GL11.glBlendFunc(blendSrcFactor, blendDstFactor); } else { - // 使用默认混合函数 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); } - // 恢复深度测试状态 - if (depthTestEnabled) { - GL11.glEnable(GL11.GL_DEPTH_TEST); - } else { - GL11.glDisable(GL11.GL_DEPTH_TEST); - } + // 恢复深度测试 + if (depthTestEnabled) GL11.glEnable(GL11.GL_DEPTH_TEST); else GL11.glDisable(GL11.GL_DEPTH_TEST); - } catch (Exception e) { + } catch (Throwable e) { logger.error("Error during state restoration: {}", e.getMessage()); + } finally { + // 清理可能的 GL error + while (GL11.glGetError() != GL11.GL_NO_ERROR) { /* clear */ } } } - /** - * 检查混合函数值是否有效 - */ private boolean isValidBlendFunc(int func) { switch (func) { case GL11.GL_ZERO: @@ -283,8 +327,9 @@ public final class RenderSystem { ", blendDstFactor=" + blendDstFactor + ", activeTexture=" + activeTexture + ", boundTexture=" + boundTexture + - ", clearColor=" + Arrays.toString(clearColor) + - ", viewport=" + Arrays.toString(viewport); + ", clearColor=" + java.util.Arrays.toString(clearColor) + + ", viewport=" + java.util.Arrays.toString(viewport) + + '}'; } } // ================== 初始化方法 ================== @@ -373,8 +418,18 @@ public final class RenderSystem { private static void _pushState() { assertOnRenderThread(); - stateStack.push(new RenderState()); - checkGLError("pushState"); + checkGLError("started pushState"); + try { + // 尝试构造 RenderState(内部会尽量安全地查询 GL) + stateStack.push(new RenderState()); + } catch (Exception e) { + // 严格容错:如果构造失败,记录并 push 一个默认状态,保证栈平衡与渲染继续 + logger.warn("Failed to push full render state, pushing fallback default state: {}", e.getMessage()); + stateStack.push(new RenderState(true)); // 调用下面新增的 fallback 构造器 + } + + // 检查并记录任何产生的 GL 错误(非致命) + checkGLError("end pushState"); } /** diff --git a/src/main/java/org/tzd/debug/GetInstance.class b/src/main/java/org/tzd/debug/GetInstance.class new file mode 100644 index 0000000000000000000000000000000000000000..4ec4fdd0eda8fd8bfd381f78d215dddf2b842d85 GIT binary patch literal 337 zcmZ`!yH3ME5S(=$#!eunq@bWdaSHAO64FEhMT(#ZqMSmTz2-ZXs- zaYQ()YAxJrAqthXTHGqPwvCfESA_nmDwrl4W7S>zoT>UjW+hifx;u6`-TXSOO4&5? z^r=%in9m8()@UnTTk{j?pQXiY`OjXwt=c+QH^zG>oiAQ_UtA#M!+iuGe>!8t**P;Y d4`%P6Hx`7L(YF~X!U$v5$D9Q?VKw +/* Header for class org_tzd_debug_GetInstance */ + +#ifndef _Included_org_tzd_debug_GetInstance +#define _Included_org_tzd_debug_GetInstance +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_tzd_debug_GetInstance + * Method: getInstance + * Signature: (Ljava/lang/Class;)[Ljava/lang/Object; + */ +JNIEXPORT jobjectArray JNICALL Java_org_tzd_debug_GetInstance_getInstance + (JNIEnv *, jclass, jclass); + +#ifdef __cplusplus +} +#endif +#endif