feat(render): 实现网格顶点预测与控制点优化功能
- 添加 previewPoint 字段支持预览点显示 - 实现 predictVerticesWithTemporarySecondary 方法用于顶点变形预测 - 引入 SNAP_THRESHOLD 和 pinnedController 支持控制点吸附逻辑- 优化 updateVerticesFromSecondaryVertices 方法的三角分配策略 - 添加 moveSecondaryVertex 方法支持控制点移动与锁定逻辑 - 集成 RegionOptimizer 优化控制点半径分配- 移除冗余的 liquify 和 puppet 渲染代码进网格 - 改变形算法稳定性与性能表现
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定位置创建二级顶点
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置原始位置
|
||||
*/
|
||||
|
||||
@@ -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) );
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
BIN
src/main/java/org/tzd/debug/GetInstance.class
Normal file
BIN
src/main/java/org/tzd/debug/GetInstance.class
Normal file
Binary file not shown.
21
src/main/java/org/tzd/debug/org_tzd_debug_GetInstance.h
Normal file
21
src/main/java/org/tzd/debug/org_tzd_debug_GetInstance.h
Normal 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
|
||||
Reference in New Issue
Block a user