feat(render): 实现动态缩放支持与文本渲染优化
- 为 BoundingBox 类添加获取中心点坐标的便捷方法 - 重构 Mesh2D 悬停提示框绘制逻辑,支持基于摄像机缩放的动态尺寸计算 - 在 ModelRender 中新增带缩放参数的文本渲染方法 - 重写 MultiSelectionBoxRenderer 以适配动态缩放,统一使用像素单位配置 - 优化 ParametersManagement 日志记录方式 - 修复 TextRenderer 字体颜色传递问题 - 更新 TextShader 着色器代码以兼容新的渲染管线和透明度处理
This commit is contained in:
@@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest2} - 进阶模型测试</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author tzdwindows
|
||||
* @author tzdwindows 7
|
||||
* @version 1.2
|
||||
* @since 2025-10-13
|
||||
*/
|
||||
@@ -982,38 +982,23 @@ public final class ModelRender {
|
||||
if (shaderList == null || shaderList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前绑定的着色器程序
|
||||
int currentProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
try {
|
||||
for (CompleteShader shader : shaderList) {
|
||||
// 跳过默认着色器
|
||||
if (shader.isDefaultShader()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取着色器程序
|
||||
ShaderProgram program = ShaderManagement.getShaderProgram(shader.getShaderName());
|
||||
if (program == null || program.programId == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
program.use();
|
||||
|
||||
// 只设置顶点坐标相关的uniform
|
||||
setUniformMatrix3(program, "uProjectionMatrix", projection);
|
||||
setUniformMatrix3(program, "uViewMatrix", view);
|
||||
|
||||
// 设置基础模型矩阵为单位矩阵
|
||||
setUniformMatrix3(program, "uModelMatrix", new Matrix3f().identity());
|
||||
|
||||
// 设置摄像机Z轴位置
|
||||
setUniformFloatInternal(program, "uCameraZ", camera.getZPosition());
|
||||
|
||||
RenderSystem.checkGLError("setupNonDefaultShaders_" + shader.getShaderName());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to setup non-default shader: {}", shader.getShaderName(), e);
|
||||
}
|
||||
@@ -1353,6 +1338,15 @@ public final class ModelRender {
|
||||
defaultTextRenderer.renderText(text, px, py, color);
|
||||
}
|
||||
|
||||
public static void renderText(String text, float x, float y, Vector4f color, float scale) {
|
||||
if (!initialized || defaultTextRenderer == null) return;
|
||||
RenderSystem.assertOnRenderThread();
|
||||
Vector2f offset = getCameraOffset();
|
||||
float px = x - offset.x;
|
||||
float py = y - offset.y;
|
||||
defaultTextRenderer.renderText(text, px, py, color, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认摄像机与当前摄像机之间的偏移量
|
||||
*
|
||||
|
||||
@@ -9,100 +9,105 @@ import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* MultiSelectionBoxRenderer — 美观版完整实现
|
||||
* MultiSelectionBoxRenderer — 美观版完整实现 (已适配动态缩放 - 边框与手柄)
|
||||
*
|
||||
* 视觉设计目标:
|
||||
* - 细腻的三层边框(外发光 -> 主边框 -> 内描边)
|
||||
* - 小巧但可见的手柄(角点与边中点略有区分)
|
||||
* - 精致的中心点(十字 + 细环)
|
||||
* - 虚线用于多选边框
|
||||
* - 批处理绘制以减少 Draw Call(尽量在一次 TRIANGLES 调用中绘制多数元素)
|
||||
*
|
||||
* 注:BufferBuilder 的 vertex(...) 方法签名与项目实现有关,示例中使用 (x,y,z,u) 占位。
|
||||
* 特性:
|
||||
* - 边框、手柄、中心点的大小都会根据视图缩放动态调整,确保在任何缩放级别下都清晰可见。
|
||||
* - 所有元素尽可能在一次 GL_TRIANGLES draw call 中完成,以提高效率。
|
||||
*/
|
||||
public class MultiSelectionBoxRenderer {
|
||||
|
||||
// -------------------- 配置常量(可调) --------------------
|
||||
// 尺寸
|
||||
public static final float DEFAULT_CORNER_SIZE = 8.0f;
|
||||
// -------------------- 配置常量 (世界单位) --------------------
|
||||
public static final float DEFAULT_DASH_LENGTH = 10.0f;
|
||||
public static final float DEFAULT_GAP_LENGTH = 6.0f;
|
||||
private static final float MAIN_BORDER_THICKNESS = 2.5f;
|
||||
|
||||
// 手柄与中心点尺寸
|
||||
private static final float HANDLE_CORNER_SIZE = DEFAULT_CORNER_SIZE;
|
||||
private static final float HANDLE_MID_SIZE = 2.8f;
|
||||
private static final float CENTER_LINE_THICKNESS = 1.2f;
|
||||
private static final float CENTER_RING_RADIUS = 5.0f;
|
||||
private static final float CENTER_RING_THICKNESS = 1.2f;
|
||||
// -------------------- 配置常量 (屏幕像素单位) --------------------
|
||||
// 这些值定义了元素在屏幕上看起来应该有多大
|
||||
private static final float PIXEL_MAIN_BORDER_THICKNESS = 2.0f; // <-- 新增:主边框的像素厚度
|
||||
private static final float PIXEL_HANDLE_CORNER_SIZE = 8.0f;
|
||||
private static final float PIXEL_HANDLE_MID_SIZE = 6.0f;
|
||||
private static final float PIXEL_CENTER_LINE_THICKNESS = 1.5f;
|
||||
private static final float PIXEL_CENTER_CROSS_RADIUS = 7.0f;
|
||||
|
||||
// 颜色(更现代、更清爽)
|
||||
public static final Vector4f DASHED_BORDER_COLOR = new Vector4f(1.0f, 0.85f, 0.0f, 1.0f); // 黄色虚线
|
||||
public static final Vector4f SOLID_BORDER_COLOR_OUTER = new Vector4f(0.0f, 0.85f, 0.95f, 0.12f); // 轻微外发光(弱透明)
|
||||
public static final Vector4f SOLID_BORDER_COLOR_MAIN = new Vector4f(0.0f, 0.92f, 0.94f, 1.0f); // 主边框(青)
|
||||
public static final Vector4f SOLID_BORDER_COLOR_INNER = new Vector4f(1.0f, 1.0f, 1.0f, 0.9f); // 内描边(接近白)
|
||||
public static final Vector4f HANDLE_COLOR = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f); // 手柄白
|
||||
public static final Vector4f MULTI_SELECTION_HANDLE_COLOR = new Vector4f(1.0f, 0.9f, 0.0f, 1.0f); // 黄色手柄
|
||||
public static final Vector4f CENTER_POINT_COLOR = new Vector4f(1.0f, 0.2f, 0.2f, 1.0f); // 中心点红
|
||||
|
||||
// -------------------- 公共绘制 API --------------------
|
||||
// 颜色
|
||||
public static final Vector4f DASHED_BORDER_COLOR = new Vector4f(1.0f, 0.85f, 0.0f, 1.0f);
|
||||
public static final Vector4f SOLID_BORDER_COLOR_MAIN = new Vector4f(0.0f, 0.92f, 0.94f, 1.0f);
|
||||
public static final Vector4f HANDLE_COLOR = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
public static final Vector4f MULTI_SELECTION_HANDLE_COLOR = new Vector4f(1.0f, 0.9f, 0.0f, 1.0f);
|
||||
public static final Vector4f CENTER_POINT_COLOR = new Vector4f(1.0f, 0.2f, 0.2f, 1.0f);
|
||||
|
||||
/**
|
||||
* 绘制单选的选择框(主入口)
|
||||
*
|
||||
* @param bounds 包围盒(世界坐标)
|
||||
* @param pivot 旋转中心 / 中心点(世界坐标)
|
||||
* @param zoom 当前摄像机的缩放值 (e.g., from ModelRender.getCamera().getZoom())
|
||||
*/
|
||||
public static void drawSelectBox(BoundingBox bounds, Vector2f pivot) {
|
||||
if (bounds == null || !bounds.isValid()) return;
|
||||
public static void drawSelectBox(BoundingBox bounds, Vector2f pivot, float zoom) {
|
||||
if (bounds == null || !bounds.isValid() || zoom <= 1e-6f) return;
|
||||
|
||||
// 根据 zoom 计算所有元素在世界坐标下的实际尺寸
|
||||
float worldBorderThickness = PIXEL_MAIN_BORDER_THICKNESS / zoom;
|
||||
float worldCornerSize = PIXEL_HANDLE_CORNER_SIZE / zoom;
|
||||
float worldMidSize = PIXEL_HANDLE_MID_SIZE / zoom;
|
||||
float worldCenterLineThickness = PIXEL_CENTER_LINE_THICKNESS / zoom;
|
||||
float worldCenterCrossRadius = PIXEL_CENTER_CROSS_RADIUS / zoom;
|
||||
|
||||
float minX = bounds.getMinX();
|
||||
float minY = bounds.getMinY();
|
||||
float maxX = bounds.getMaxX();
|
||||
float maxY = bounds.getMaxY();
|
||||
|
||||
Tesselator tesselator = Tesselator.getInstance();
|
||||
BufferBuilder bb = tesselator.getBuilder();
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
float scaledCornerSize = Math.max(4.0f, DEFAULT_CORNER_SIZE);
|
||||
float scaledMidSize = Math.max(2.0f, HANDLE_MID_SIZE);
|
||||
bb.begin(GL11.GL_LINES, 8); // 4条线 * 2个顶点 = 8个顶点
|
||||
bb.setColor(SOLID_BORDER_COLOR_MAIN);
|
||||
addLineVertices(bb, minX, minY, maxX, minY); // 上边
|
||||
addLineVertices(bb, maxX, minY, maxX, maxY); // 右边
|
||||
addLineVertices(bb, maxX, maxY, minX, maxY); // 下边
|
||||
addLineVertices(bb, minX, maxY, minX, minY); // 左边
|
||||
tesselator.end();
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, (8 + 2) * 6);
|
||||
bb.setColor(HANDLE_COLOR);
|
||||
addHandleQuad(bb, minX, minY, scaledCornerSize);
|
||||
addHandleQuad(bb, maxX, minY, scaledCornerSize);
|
||||
addHandleQuad(bb, minX, maxY, scaledCornerSize);
|
||||
addHandleQuad(bb, maxX, maxY, scaledCornerSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, scaledMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, scaledMidSize);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, scaledMidSize);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, scaledMidSize);
|
||||
bb.setColor(CENTER_POINT_COLOR);
|
||||
addQuadLine(bb, pivot.x - 6.0f, pivot.y, pivot.x + 6.0f, pivot.y, CENTER_LINE_THICKNESS);
|
||||
addQuadLine(bb, pivot.x, pivot.y - 6.0f, pivot.x, pivot.y + 6.0f, CENTER_LINE_THICKNESS);
|
||||
tesselator.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加简单的线段顶点(GL_LINES模式)
|
||||
*/
|
||||
private static void addLineVertices(BufferBuilder bb, float x0, float y0, float x1, float y1) {
|
||||
bb.vertex(x0, y0, 0.0f, 0.0f);
|
||||
bb.vertex(x1, y1, 0.0f, 0.0f);
|
||||
// 将所有绘制合并到一次 TRIANGLES 调用中
|
||||
// 预估顶点数:4条边*6 + 8个手柄*6 + 中心十字*6*2 = 24 + 48 + 12 = 84
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, 96);
|
||||
|
||||
// 1. 绘制有厚度的边框
|
||||
bb.setColor(SOLID_BORDER_COLOR_MAIN);
|
||||
addQuadLine(bb, minX, minY, maxX, minY, worldBorderThickness); // 上边
|
||||
addQuadLine(bb, maxX, minY, maxX, maxY, worldBorderThickness); // 右边
|
||||
addQuadLine(bb, maxX, maxY, minX, maxY, worldBorderThickness); // 下边
|
||||
addQuadLine(bb, minX, maxY, minX, minY, worldBorderThickness); // 左边
|
||||
|
||||
// 2. 绘制手柄
|
||||
bb.setColor(HANDLE_COLOR);
|
||||
addHandleQuad(bb, minX, minY, worldCornerSize);
|
||||
addHandleQuad(bb, maxX, minY, worldCornerSize);
|
||||
addHandleQuad(bb, minX, maxY, worldCornerSize);
|
||||
addHandleQuad(bb, maxX, maxY, worldCornerSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, worldMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, worldMidSize);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, worldMidSize);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, worldMidSize);
|
||||
|
||||
// 3. 绘制中心点
|
||||
bb.setColor(CENTER_POINT_COLOR);
|
||||
addQuadLine(bb, pivot.x - worldCenterCrossRadius, pivot.y, pivot.x + worldCenterCrossRadius, pivot.y, worldCenterLineThickness);
|
||||
addQuadLine(bb, pivot.x, pivot.y - worldCenterCrossRadius, pivot.x, pivot.y + worldCenterCrossRadius, worldCenterLineThickness);
|
||||
|
||||
tesselator.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制多选框(虚线 + 手柄)
|
||||
*
|
||||
* @param multiBounds 多选包围盒
|
||||
* @param zoom 当前摄像机的缩放值
|
||||
*/
|
||||
public static void drawMultiSelectionBox(BoundingBox multiBounds) {
|
||||
if (multiBounds == null || !multiBounds.isValid()) return;
|
||||
public static void drawMultiSelectionBox(BoundingBox multiBounds, float zoom) {
|
||||
if (multiBounds == null || !multiBounds.isValid() || zoom <= 1e-6f) return;
|
||||
|
||||
// 根据 zoom 计算所有元素在世界坐标下的实际尺寸
|
||||
float worldBorderThickness = PIXEL_MAIN_BORDER_THICKNESS / zoom;
|
||||
float worldCornerSize = PIXEL_HANDLE_CORNER_SIZE / zoom;
|
||||
float worldMidSize = PIXEL_HANDLE_MID_SIZE / zoom;
|
||||
float worldCenterLineThickness = PIXEL_CENTER_LINE_THICKNESS / zoom;
|
||||
float worldCenterCrossRadius = PIXEL_CENTER_CROSS_RADIUS / zoom;
|
||||
|
||||
float minX = multiBounds.getMinX();
|
||||
float minY = multiBounds.getMinY();
|
||||
@@ -111,157 +116,52 @@ public class MultiSelectionBoxRenderer {
|
||||
|
||||
Tesselator tesselator = Tesselator.getInstance();
|
||||
BufferBuilder bb = tesselator.getBuilder();
|
||||
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// 1) 虚线边框(使用 GL_LINES)
|
||||
// 合并所有绘制
|
||||
int estimatedSegments = Math.max(4, (int) Math.ceil((2f * (multiBounds.getWidth() + multiBounds.getHeight())) / (DEFAULT_DASH_LENGTH + DEFAULT_GAP_LENGTH)));
|
||||
bb.begin(GL11.GL_LINES, estimatedSegments * 2);
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, estimatedSegments * 6 * 4 + 96); // 넉넉하게 할당
|
||||
|
||||
// 1. 绘制有厚度的虚线边框
|
||||
bb.setColor(DASHED_BORDER_COLOR);
|
||||
addDashedLineVertices(bb, minX, minY, maxX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
addDashedLineVertices(bb, maxX, minY, maxX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
addDashedLineVertices(bb, maxX, maxY, minX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
addDashedLineVertices(bb, minX, maxY, minX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH);
|
||||
tesselator.end();
|
||||
addThickDashedLine(bb, minX, minY, maxX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH, worldBorderThickness);
|
||||
addThickDashedLine(bb, maxX, minY, maxX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH, worldBorderThickness);
|
||||
addThickDashedLine(bb, maxX, maxY, minX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH, worldBorderThickness);
|
||||
addThickDashedLine(bb, minX, maxY, minX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH, worldBorderThickness);
|
||||
|
||||
// 2) 手柄与中心(合并为一次三角形绘制)
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, (8 + 2) * 6);
|
||||
// 2. 绘制手柄
|
||||
bb.setColor(MULTI_SELECTION_HANDLE_COLOR);
|
||||
addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE);
|
||||
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE);
|
||||
addHandleQuad(bb, minX, minY, worldCornerSize);
|
||||
addHandleQuad(bb, maxX, minY, worldCornerSize);
|
||||
addHandleQuad(bb, minX, maxY, worldCornerSize);
|
||||
addHandleQuad(bb, maxX, maxY, worldCornerSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, minY, worldMidSize);
|
||||
addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, worldMidSize);
|
||||
addHandleQuad(bb, minX, (minY + maxY) * 0.5f, worldMidSize);
|
||||
addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, worldMidSize);
|
||||
|
||||
// 3. 绘制中心点
|
||||
Vector2f center = multiBounds.getCenter();
|
||||
bb.setColor(CENTER_POINT_COLOR);
|
||||
addQuadLine(bb, center.x - 6.0f, center.y, center.x + 6.0f, center.y, CENTER_LINE_THICKNESS);
|
||||
addQuadLine(bb, center.x, center.y - 6.0f, center.x, center.y + 6.0f, CENTER_LINE_THICKNESS);
|
||||
addQuadLine(bb, center.x - worldCenterCrossRadius, center.y, center.x + worldCenterCrossRadius, center.y, worldCenterLineThickness);
|
||||
addQuadLine(bb, center.x, center.y - worldCenterCrossRadius, center.x, center.y + worldCenterCrossRadius, worldCenterLineThickness);
|
||||
|
||||
tesselator.end();
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 辅助绘图方法 --------------------
|
||||
|
||||
/**
|
||||
* 添加一个填充四边形(用两个三角形表示)
|
||||
*/
|
||||
private static void addFilledQuad(BufferBuilder bb, float x0, float y0, float x1, float y1) {
|
||||
// tri 1
|
||||
bb.vertex(x0, y0, 0.0f, 0.0f);
|
||||
bb.vertex(x1, y0, 0.0f, 0.0f);
|
||||
bb.vertex(x1, y1, 0.0f, 0.0f);
|
||||
// tri 2
|
||||
bb.vertex(x1, y1, 0.0f, 0.0f);
|
||||
bb.vertex(x0, y1, 0.0f, 0.0f);
|
||||
bb.vertex(x0, y0, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手柄:以中心点绘制正方形手柄(填充)
|
||||
*/
|
||||
private static void addHandleQuad(BufferBuilder bb, float cx, float cy, float size) {
|
||||
float half = size * 0.5f;
|
||||
addFilledQuad(bb, cx - half, cy - half, cx + half, cy + half);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一条由四边形模拟的线段(厚度可控)
|
||||
*/
|
||||
private static void addQuadLine(BufferBuilder bb, float x0, float y0, float x1, float y1, float thickness) {
|
||||
float dx = x1 - x0;
|
||||
float dy = y1 - y0;
|
||||
float len = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
if (len < 1e-6f) return;
|
||||
|
||||
float halfThick = thickness * 0.5f;
|
||||
float nx = -dy / len * halfThick;
|
||||
float ny = dx / len * halfThick;
|
||||
|
||||
float v0x = x0 + nx; float v0y = y0 + ny;
|
||||
float v1x = x1 + nx; float v1y = y1 + ny;
|
||||
float v2x = x1 - nx; float v2y = y1 - ny;
|
||||
float v3x = x0 - nx; float v3y = y0 - ny;
|
||||
|
||||
bb.vertex(v0x, v0y, 0.0f, 0.0f);
|
||||
bb.vertex(v1x, v1y, 0.0f, 0.0f);
|
||||
bb.vertex(v2x, v2y, 0.0f, 0.0f);
|
||||
|
||||
bb.vertex(v2x, v2y, 0.0f, 0.0f);
|
||||
bb.vertex(v3x, v3y, 0.0f, 0.0f);
|
||||
bb.vertex(v0x, v0y, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绘制一个闭合的四边形线环(用于边框三层绘制)
|
||||
* (新增) 在两点之间生成有厚度的虚线段 (使用 GL_TRIANGLES)
|
||||
*
|
||||
* @param thickness 厚度(世界坐标)
|
||||
* @param vertices 顶点序列 x1,y1,x2,y2,...
|
||||
* @param dashLen 虚线长度(世界坐标)
|
||||
* @param gapLen 间隙长度(世界坐标)
|
||||
* @param thickness 虚线的厚度(世界坐标)
|
||||
*/
|
||||
private static void addQuadLineLoop(BufferBuilder bb, float thickness, float... vertices) {
|
||||
if (vertices == null || vertices.length < 8) return;
|
||||
int n = vertices.length / 2;
|
||||
for (int i = 0; i < n; i++) {
|
||||
float x0 = vertices[(i * 2)];
|
||||
float y0 = vertices[(i * 2) + 1];
|
||||
float x1 = vertices[((i + 1) % n) * 2];
|
||||
float y1 = vertices[((i + 1) % n) * 2 + 1];
|
||||
addQuadLine(bb, x0, y0, x1, y1, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制圆环(由多个三角形片段组成)
|
||||
*
|
||||
* @param cx 中心 x
|
||||
* @param cy 中心 y
|
||||
* @param radius 半径
|
||||
* @param thickness 环厚度
|
||||
* @param segments 分段数(建议 >= 8)
|
||||
*/
|
||||
private static void addRing(BufferBuilder bb, float cx, float cy, float radius, float thickness, int segments) {
|
||||
if (segments < 6) segments = 6;
|
||||
float halfThick = thickness * 0.5f;
|
||||
float innerR = Math.max(0.5f, radius - halfThick);
|
||||
float outerR = radius + halfThick;
|
||||
|
||||
for (int i = 0; i < segments; i++) {
|
||||
float a0 = (float) (i * 2.0 * Math.PI / segments);
|
||||
float a1 = (float) ((i + 1) * 2.0 * Math.PI / segments);
|
||||
|
||||
float cos0 = (float) Math.cos(a0), sin0 = (float) Math.sin(a0);
|
||||
float cos1 = (float) Math.cos(a1), sin1 = (float) Math.sin(a1);
|
||||
|
||||
float x0i = cx + cos0 * innerR, y0i = cy + sin0 * innerR;
|
||||
float x1i = cx + cos1 * innerR, y1i = cy + sin1 * innerR;
|
||||
float x0o = cx + cos0 * outerR, y0o = cy + sin0 * outerR;
|
||||
float x1o = cx + cos1 * outerR, y1o = cy + sin1 * outerR;
|
||||
|
||||
// tri 1
|
||||
bb.vertex(x0i, y0i, 0.0f, 0.0f);
|
||||
bb.vertex(x0o, y0o, 0.0f, 0.0f);
|
||||
bb.vertex(x1o, y1o, 0.0f, 0.0f);
|
||||
// tri 2
|
||||
bb.vertex(x1o, y1o, 0.0f, 0.0f);
|
||||
bb.vertex(x1i, y1i, 0.0f, 0.0f);
|
||||
bb.vertex(x0i, y0i, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在两点之间生成虚线段顶点(使用 GL_LINES)
|
||||
*
|
||||
* @param dashLen 虚线长度(世界坐标)
|
||||
* @param gapLen 间隙长度(世界坐标)
|
||||
*/
|
||||
private static void addDashedLineVertices(BufferBuilder bb, float startX, float startY, float endX, float endY,
|
||||
float dashLen, float gapLen) {
|
||||
private static void addThickDashedLine(BufferBuilder bb, float startX, float startY, float endX, float endY,
|
||||
float dashLen, float gapLen, float thickness) {
|
||||
float dx = endX - startX;
|
||||
float dy = endY - startY;
|
||||
float len = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
@@ -281,8 +181,60 @@ public class MultiSelectionBoxRenderer {
|
||||
float ex = startX + dirX * e;
|
||||
float ey = startY + dirY * e;
|
||||
|
||||
bb.vertex(sx, sy, 0.0f, 0.0f);
|
||||
bb.vertex(ex, ey, 0.0f, 0.0f);
|
||||
// 为每一小段虚线绘制一个有厚度的四边形
|
||||
addQuadLine(bb, sx, sy, ex, ey, thickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手柄:以中心点绘制正方形手柄(填充)
|
||||
*/
|
||||
private static void addHandleQuad(BufferBuilder bb, float cx, float cy, float size) {
|
||||
float half = size * 0.5f;
|
||||
addFilledQuad(bb, cx - half, cy - half, cx + half, cy + half);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一条由四边形模拟的线段(厚度可控)
|
||||
*/
|
||||
public static void addQuadLine(BufferBuilder bb, float x0, float y0, float x1, float y1, float thickness) {
|
||||
float dx = x1 - x0;
|
||||
float dy = y1 - y0;
|
||||
float len = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
if (len < 1e-6f) return;
|
||||
|
||||
float halfThick = thickness * 0.5f;
|
||||
// 计算线段的法线向量
|
||||
float nx = -dy / len * halfThick;
|
||||
float ny = dx / len * halfThick;
|
||||
|
||||
// 计算四边形的四个顶点
|
||||
float v0x = x0 + nx; float v0y = y0 + ny;
|
||||
float v1x = x1 + nx; float v1y = y1 + ny;
|
||||
float v2x = x1 - nx; float v2y = y1 - ny;
|
||||
float v3x = x0 - nx; float v3y = y0 - ny;
|
||||
|
||||
addQuadVertices(bb, v0x, v0y, v1x, v1y, v2x, v2y, v3x, v3y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个填充四边形(用两个三角形表示)
|
||||
*/
|
||||
public static void addFilledQuad(BufferBuilder bb, float x0, float y0, float x1, float y1) {
|
||||
addQuadVertices(bb, x0, y0, x1, y0, x1, y1, x0, y1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:添加构成四边形的6个顶点
|
||||
*/
|
||||
private static void addQuadVertices(BufferBuilder bb, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
|
||||
// tri 1
|
||||
bb.vertex(x0, y0, 0.0f, 0.0f);
|
||||
bb.vertex(x1, y1, 0.0f, 0.0f);
|
||||
bb.vertex(x2, y2, 0.0f, 0.0f);
|
||||
// tri 2
|
||||
bb.vertex(x2, y2, 0.0f, 0.0f);
|
||||
bb.vertex(x3, y3, 0.0f, 0.0f);
|
||||
bb.vertex(x0, y0, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,7 @@ public final class TextRenderer {
|
||||
// 按字符类型分组渲染以减少纹理切换
|
||||
int currentTexture = -1;
|
||||
boolean batchStarted = false;
|
||||
builder.setColor(color);
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
|
||||
@@ -19,19 +19,17 @@ import java.util.*;
|
||||
public class ParametersManagement {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ParametersManagement.class);
|
||||
private final ParametersPanel parametersPanel;
|
||||
private final ModelRenderPanel renderPanel;
|
||||
public List<Parameter> oldValues = new ArrayList<>();
|
||||
|
||||
public ParametersManagement(ParametersPanel parametersPanel) {
|
||||
this.parametersPanel = parametersPanel;
|
||||
this.renderPanel = parametersPanel.getRenderPanel();
|
||||
|
||||
ModelRenderPanel renderPanel = parametersPanel.getRenderPanel();
|
||||
for (int i = 0; i < renderPanel.getModel().getParts().size(); i++) {
|
||||
ModelPart modelPart = renderPanel.getModel().getParts().get(i);
|
||||
modelPart.addEvent((eventName, eventBus) -> {
|
||||
if (eventName.equals("vertex")){
|
||||
if (!(eventBus instanceof Map)) {
|
||||
System.err.println("Error: eventBus is not a Map for vertex event.");
|
||||
logger.error("Error: eventBus is not a Map for vertex event.");
|
||||
return;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1561,54 +1561,64 @@ public class Mesh2D {
|
||||
|
||||
if (isSuspension && !selected) {
|
||||
RenderSystem.pushState();
|
||||
|
||||
setSolidShader(modelMatrix);
|
||||
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
Tesselator t = Tesselator.getInstance();
|
||||
BufferBuilder bb = t.getBuilder();
|
||||
|
||||
BoundingBox bbox = getBounds();
|
||||
if (bbox != null && bbox.isValid()) {
|
||||
bb.begin(GL11.GL_LINE_LOOP, 4);
|
||||
float zoom = ModelRender.getCamera().getZoom();
|
||||
|
||||
if (bbox != null && bbox.isValid() && zoom > 1e-6f) {
|
||||
|
||||
// --- 1. 定义所有元素的期望“像素”尺寸 ---
|
||||
final float PIXEL_BORDER_THICKNESS = 1.5f;
|
||||
final float PIXEL_BOX_OFFSET_Y = 8.0f; // 提示框距离物体顶部的垂直像素距离
|
||||
final float PIXEL_FONT_SIZE_BASE = 14.0f;
|
||||
final float PIXEL_LINE_HEIGHT = 18.0f;
|
||||
final float PIXEL_PADDING = 5.0f; // 文本背景框的内边距
|
||||
|
||||
// --- 2. 根据 zoom 计算出在“世界坐标”中应有的大小 ---
|
||||
float worldBorderThickness = PIXEL_BORDER_THICKNESS / zoom;
|
||||
float worldBoxOffsetY = PIXEL_BOX_OFFSET_Y / zoom;
|
||||
float worldLineHeight = PIXEL_LINE_HEIGHT / zoom;
|
||||
float worldPadding = PIXEL_PADDING / zoom;
|
||||
float textScale = (PIXEL_FONT_SIZE_BASE / 14.0f) / zoom;
|
||||
|
||||
// --- 3. 绘制动态厚度的红色悬停边框 ---
|
||||
bb.begin(GL11.GL_TRIANGLES, 4 * 6);
|
||||
bb.setColor(new Vector4f(1f, 0f, 0f, 1f));
|
||||
bb.vertex(bbox.getMinX(), bbox.getMinY(), 0f, 0f);
|
||||
bb.vertex(bbox.getMaxX(), bbox.getMinY(), 0f, 0f);
|
||||
bb.vertex(bbox.getMaxX(), bbox.getMaxY(), 0f, 0f);
|
||||
bb.vertex(bbox.getMinX(), bbox.getMaxY(), 0f, 0f);
|
||||
MultiSelectionBoxRenderer.addQuadLine(bb, bbox.getMinX(), bbox.getMinY(), bbox.getMaxX(), bbox.getMinY(), worldBorderThickness);
|
||||
MultiSelectionBoxRenderer.addQuadLine(bb, bbox.getMaxX(), bbox.getMinY(), bbox.getMaxX(), bbox.getMaxY(), worldBorderThickness);
|
||||
MultiSelectionBoxRenderer.addQuadLine(bb, bbox.getMaxX(), bbox.getMaxY(), bbox.getMinX(), bbox.getMaxY(), worldBorderThickness);
|
||||
MultiSelectionBoxRenderer.addQuadLine(bb, bbox.getMinX(), bbox.getMaxY(), bbox.getMinX(), bbox.getMinY(), worldBorderThickness);
|
||||
t.end();
|
||||
|
||||
// --- 4. 计算文本布局 ---
|
||||
String hoverText = getName();
|
||||
float textX = bbox.getMaxX() + 5f;
|
||||
float textY = bbox.getMaxY();
|
||||
Vector4f bgColor = new Vector4f(1f, 0f, 0f, 0.8f);
|
||||
Vector4f fgColor = new Vector4f(1f, 1f, 1f, 1f);
|
||||
|
||||
float lineHeight = 18f;
|
||||
|
||||
List<String> lines = splitLines(hoverText, 30);
|
||||
|
||||
float textHeight = lines.size() * lineHeight;
|
||||
float textWidth = 0f;
|
||||
float maxTextWidth = 0f;
|
||||
for (String line : lines) {
|
||||
textWidth = Math.max(textWidth, ModelRender.getTextRenderer().getTextWidth(line));
|
||||
maxTextWidth = Math.max(maxTextWidth, ModelRender.getTextRenderer().getTextWidth(line) * textScale);
|
||||
}
|
||||
|
||||
float totalTextHeight = (lines.size() -1) * worldLineHeight;
|
||||
float boxX = bbox.getCenterX() - (maxTextWidth / 2f);
|
||||
float boxY = bbox.getMaxY() + worldBoxOffsetY;
|
||||
Vector4f bgColor = new Vector4f(1f, 0f, 0f, 0.8f);
|
||||
bb.begin(GL11.GL_TRIANGLES, 6);
|
||||
bb.setColor(bgColor);
|
||||
bb.vertex(textX, textY, 0f, 0f);
|
||||
bb.vertex(textX + textWidth, textY, 0f, 0f);
|
||||
bb.vertex(textX + textWidth, textY + textHeight, 0f, 0f);
|
||||
bb.vertex(textX + textWidth, textY + textHeight, 0f, 0f);
|
||||
bb.vertex(textX, textY + textHeight, 0f, 0f);
|
||||
bb.vertex(textX, textY, 0f, 0f);
|
||||
float bgX0 = boxX - worldPadding;
|
||||
float bgY0 = boxY - worldPadding;
|
||||
float bgX1 = boxX + maxTextWidth + worldPadding;
|
||||
float bgY1 = boxY + totalTextHeight + worldPadding + worldLineHeight;
|
||||
MultiSelectionBoxRenderer.addFilledQuad(bb, bgX0, bgY0, bgX1, bgY1);
|
||||
t.end();
|
||||
|
||||
Vector4f fgColor = new Vector4f(1f, 1f, 1f, 1f);
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
ModelRender.renderText(line, textX, textY + (i + 1) * lineHeight - 5, fgColor);
|
||||
float lineY = boxY + (lines.size() - 1 - i) * worldLineHeight;
|
||||
ModelRender.renderText(line, boxX, lineY + 30.0f, fgColor, textScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1665,7 +1675,7 @@ public class Mesh2D {
|
||||
|
||||
private void drawSelectBox() {
|
||||
BoundingBox bounds = getBounds();
|
||||
MultiSelectionBoxRenderer.drawSelectBox(bounds, pivot);
|
||||
MultiSelectionBoxRenderer.drawSelectBox(bounds, pivot, ModelRender.getCamera().getZoom());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1774,7 +1784,7 @@ public class Mesh2D {
|
||||
*/
|
||||
private void drawMultiSelectionBox() {
|
||||
BoundingBox multiBounds = getMultiSelectionBounds();
|
||||
MultiSelectionBoxRenderer.drawMultiSelectionBox(multiBounds);
|
||||
MultiSelectionBoxRenderer.drawMultiSelectionBox(multiBounds,ModelRender.getCamera().getZoom());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -630,4 +630,20 @@ public class BoundingBox {
|
||||
public BoundingBox copy() {
|
||||
return new BoundingBox(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包围盒的中心点 X 坐标。
|
||||
* @return 中心点的 X 坐标
|
||||
*/
|
||||
public float getCenterX() {
|
||||
return (this.minX + this.maxX) * 0.5f;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包围盒的中心点 Y 坐标。
|
||||
* @return 中心点的 Y 坐标
|
||||
*/
|
||||
public float getCenterY() {
|
||||
return (this.minY + this.maxY) * 0.5f;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
/**
|
||||
* 文本着色器
|
||||
* 文本着色器 (已修正并与渲染引擎兼容)
|
||||
*
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
@@ -14,11 +14,6 @@ public class TextShader implements CompleteShader {
|
||||
|
||||
private final VertexShader vertexShader = new VertexShader();
|
||||
private final FragmentShader fragmentShader = new FragmentShader();
|
||||
private final Vector4f color = new Vector4f(1, 1, 1, 1);
|
||||
|
||||
public void setColor(Vector4f color) {
|
||||
this.color.set(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shader getVertexShader() {
|
||||
@@ -42,12 +37,11 @@ public class TextShader implements CompleteShader {
|
||||
|
||||
@Override
|
||||
public void setDefaultUniforms(ShaderProgram program) {
|
||||
// 传递颜色 uniform
|
||||
program.setUniform4f("uColor", color.x, color.y, color.z, color.w);
|
||||
// 纹理通常绑定到0号纹理单元
|
||||
program.setUniform4f("uColor", 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
program.setUniform1i("uTexture", 0);
|
||||
}
|
||||
|
||||
// --- Vertex Shader (已适配 mat3 和 uCameraZ) ---
|
||||
private static class VertexShader implements Shader {
|
||||
@Override
|
||||
public String getShaderCode() {
|
||||
@@ -56,12 +50,18 @@ public class TextShader implements CompleteShader {
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
|
||||
uniform mat3 uModelMatrix;
|
||||
uniform mat3 uViewMatrix;
|
||||
uniform mat3 uProjectionMatrix;
|
||||
|
||||
uniform float uCameraZ;
|
||||
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
vec3 p = uProjectionMatrix * vec3(aPosition, 1.0);
|
||||
vec3 p = uProjectionMatrix * uViewMatrix * uModelMatrix * vec3(aPosition, 1.0);
|
||||
|
||||
// 输出为 gl_Position (vec4)
|
||||
gl_Position = vec4(p.xy, 0.0, 1.0);
|
||||
vTexCoord = aTexCoord;
|
||||
}
|
||||
@@ -86,9 +86,11 @@ public class TextShader implements CompleteShader {
|
||||
uniform vec4 uColor;
|
||||
|
||||
void main() {
|
||||
// 使用 .r 通道读取单通道纹理
|
||||
float alpha = texture(uTexture, vTexCoord).r;
|
||||
FragColor = vec4(uColor.rgb, uColor.a * alpha);
|
||||
if (FragColor.a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
@@ -98,4 +100,4 @@ public class TextShader implements CompleteShader {
|
||||
return "TextFragmentShader";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user