feat(render): 实现网格顶点预测与控制点优化功能

- 添加 previewPoint 字段支持预览点显示
- 实现 predictVerticesWithTemporarySecondary 方法用于顶点变形预测
- 引入 SNAP_THRESHOLD 和 pinnedController 支持控制点吸附逻辑- 优化 updateVerticesFromSecondaryVertices 方法的三角分配策略
- 添加 moveSecondaryVertex 方法支持控制点移动与锁定逻辑
- 集成 RegionOptimizer 优化控制点半径分配- 移除冗余的 liquify 和 puppet 渲染代码进网格
- 改变形算法稳定性与性能表现
This commit is contained in:
tzdwindows 7
2025-10-26 18:37:55 +08:00
parent 401263cd2b
commit f2cb74379e
12 changed files with 1693 additions and 727 deletions

View File

@@ -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());
}
/**
* 在指定位置创建二级顶点
*/

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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<Integer, Float> 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;
}
/**
* 设置原始位置
*/

View File

@@ -0,0 +1,191 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import java.util.List;
/**
* RegionOptimizer
* - 处理当新点靠近已有点时的 controlRadius 重新分配(避免两个点控制区完全相同且重叠)
* - 提供针对特征区域(例如嘴巴、尾巴)的简单优化算法接口
* <p>
* 算法思想(简述):
* - 当新点插入或靠近已有点时,对两点及其邻域进行局部半径重分配,保证半径不相等且满足最小/最大约束。
* - 对于特征MOUTH/TAIL使用基于位置的权重缩放半径例如嘴巴中间半径较小以保证细节边缘半径较大
*/
public class RegionOptimizer {
public enum FeatureType { MOUTH, TAIL, OTHER }
// 新点插入时处理(调用 resolveNewAndNeighbor 或 resolveForInsertedVertex
public static void resolveForInsertedVertex(SecondaryVertex newV, List<SecondaryVertex> 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<SecondaryVertex> 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<SecondaryVertex> 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<SecondaryVertex> 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<SecondaryVertex> 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<SecondaryVertex> all, int radiusNeighbors) {
if (v == null || all == null) return;
// 取最近 couple 个(这里用固定 4 个邻居作为平滑范围)
java.util.List<SecondaryVertex> 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) );
}
}

View File

@@ -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);
}
}

View File

@@ -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<String, Boolean> 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<Integer, Float> 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<Integer, Float> 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));
}
}
}

View File

@@ -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<String, Boolean> 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<SecondaryVertex> 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<SecondaryVertex> 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));
}
}
}

View File

@@ -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");
}
/**

Binary file not shown.

View File

@@ -0,0 +1,21 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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