Compare commits
2 Commits
41c3afecc8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe4142902c | ||
|
|
0d2bb8d6f7 |
@@ -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,57 +9,49 @@ 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_BORDER_THICKNESS = 2.5f;
|
||||
// -------------------- 配置常量 (世界单位) --------------------
|
||||
public static final float DEFAULT_DASH_LENGTH = 10.0f;
|
||||
public static final float DEFAULT_GAP_LENGTH = 6.0f;
|
||||
|
||||
// 视觉厚度分层(更细腻)
|
||||
private static final float OUTER_BORDER_THICKNESS = 2.2f;
|
||||
private static final float MAIN_BORDER_THICKNESS = 0.6f;
|
||||
private static final float INNER_BORDER_THICKNESS = 0.2f;
|
||||
// -------------------- 配置常量 (屏幕像素单位) --------------------
|
||||
// 这些值定义了元素在屏幕上看起来应该有多大
|
||||
private static final float 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;
|
||||
|
||||
// 手柄与中心点尺寸
|
||||
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;
|
||||
|
||||
// 颜色(更现代、更清爽)
|
||||
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();
|
||||
@@ -68,44 +60,35 @@ 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);
|
||||
|
||||
// 估算顶点数:三层边框 + 8 个手柄 + 中心点十字和圆环
|
||||
// 每个四边形使用 6 个顶点(两个三角形)
|
||||
int estimatedQuads = (3 * 4) + 8 + (6 * 4); // 近似估算(保守)
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, estimatedQuads * 6);
|
||||
// 将所有绘制合并到一次 TRIANGLES 调用中
|
||||
// 预估顶点数:4条边*6 + 8个手柄*6 + 中心十字*6*2 = 24 + 48 + 12 = 84
|
||||
bb.begin(RenderSystem.GL_TRIANGLES, 96);
|
||||
|
||||
// 外层发光边框(较宽、透明)
|
||||
bb.setColor(SOLID_BORDER_COLOR_OUTER);
|
||||
addQuadLineLoop(bb, OUTER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
|
||||
// 主边框(核心线)
|
||||
// 1. 绘制有厚度的边框
|
||||
bb.setColor(SOLID_BORDER_COLOR_MAIN);
|
||||
addQuadLineLoop(bb, MAIN_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
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); // 左边
|
||||
|
||||
// 内描边(细)
|
||||
bb.setColor(SOLID_BORDER_COLOR_INNER);
|
||||
addQuadLineLoop(bb, INNER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY);
|
||||
|
||||
// 手柄(角点与边中点)
|
||||
// 2. 绘制手柄
|
||||
bb.setColor(HANDLE_COLOR);
|
||||
addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE);
|
||||
addHandleQuad(bb, minX, 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);
|
||||
|
||||
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);
|
||||
|
||||
// 中心点:十字 + 环(圆环使用三角片段)
|
||||
// 3. 绘制中心点
|
||||
bb.setColor(CENTER_POINT_COLOR);
|
||||
addQuadLine(bb, pivot.x - 6.0f, pivot.y, pivot.x + 6.0f, pivot.y, CENTER_LINE_THICKNESS);
|
||||
addQuadLine(bb, pivot.x, pivot.y - 6.0f, pivot.x, pivot.y + 6.0f, CENTER_LINE_THICKNESS);
|
||||
addRing(bb, pivot.x, pivot.y, CENTER_RING_RADIUS, CENTER_RING_THICKNESS, 18);
|
||||
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();
|
||||
}
|
||||
@@ -114,9 +97,17 @@ public class MultiSelectionBoxRenderer {
|
||||
* 绘制多选框(虚线 + 手柄)
|
||||
*
|
||||
* @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();
|
||||
@@ -125,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;
|
||||
|
||||
// tri1
|
||||
bb.vertex(v3x, v3y, 0.0f, 0.0f);
|
||||
bb.vertex(v0x, v0y, 0.0f, 0.0f);
|
||||
bb.vertex(v1x, v1y, 0.0f, 0.0f);
|
||||
// tri2
|
||||
bb.vertex(v1x, v1y, 0.0f, 0.0f);
|
||||
bb.vertex(v2x, v2y, 0.0f, 0.0f);
|
||||
bb.vertex(v3x, v3y, 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 < 4) 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);
|
||||
@@ -295,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);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.chuangzhou.vivid2D.render.awt.manager;
|
||||
|
||||
import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel;
|
||||
import com.chuangzhou.vivid2D.render.awt.ParametersPanel;
|
||||
import com.chuangzhou.vivid2D.render.awt.manager.data.LayerOperationManagerData;
|
||||
import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData;
|
||||
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
||||
import com.chuangzhou.vivid2D.render.model.ModelEvent;
|
||||
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Vertex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -20,8 +23,96 @@ public class ParametersManagement {
|
||||
|
||||
public ParametersManagement(ParametersPanel parametersPanel) {
|
||||
this.parametersPanel = parametersPanel;
|
||||
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)) {
|
||||
logger.error("Error: eventBus is not a Map for vertex event.");
|
||||
return;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> eventPayload = (Map<String, Object>) eventBus;
|
||||
ModelPart caller = (ModelPart) eventPayload.get("caller");
|
||||
Vertex oldVertexObj = (Vertex) eventPayload.get("oldVertex");
|
||||
Vertex newVertexObj = (Vertex) eventPayload.get("newVertex");
|
||||
updateVertex(caller, oldVertexObj, newVertexObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找并替换一个顶点的关键帧数据。
|
||||
* 它会基于旧的顶点状态(oldVertexObj)在当前选定的关键帧上查找一个完全匹配的记录。
|
||||
* - 如果找到,则用新顶点状态(newVertexObj)的数据替换它。
|
||||
* - 如果没有找到,则调用 broadcast 创建一个新的关键帧记录。
|
||||
*
|
||||
* @param caller 触发事件的 ModelPart
|
||||
* @param oldVertexObj 变化前的 Vertex 状态,用于查找要替换的目标
|
||||
* @param newVertexObj 变化后的 Vertex 状态,用于提供新的数据
|
||||
*/
|
||||
public void updateVertex(ModelPart caller, Vertex oldVertexObj, Vertex newVertexObj) {
|
||||
if (newVertexObj == null || newVertexObj.getName() == null || oldVertexObj == null || oldVertexObj.getName() == null) {
|
||||
return;
|
||||
}
|
||||
boolean updatedExisting = false;
|
||||
for (int i = 0; i < oldValues.size(); i++) {
|
||||
Parameter existingParameter = oldValues.get(i);
|
||||
if (existingParameter.modelPart().equals(caller)) {
|
||||
List<Object> values = existingParameter.value();
|
||||
for (int j = 0; j < values.size(); j++) {
|
||||
if (!"meshVertices".equals(existingParameter.paramId().get(j))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = values.get(j);
|
||||
if (!(value instanceof Map)) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> payload = (Map<String, Object>) value;
|
||||
String storedVertexId = (String) payload.get("id");
|
||||
float[] storedPosition = (float[]) payload.get("Vertex");
|
||||
if (!Objects.equals(storedVertexId, oldVertexObj.getName())) {
|
||||
continue;
|
||||
}
|
||||
if (storedPosition == null || storedPosition.length != 2) {
|
||||
continue;
|
||||
}
|
||||
final float epsilon = 1e-5f;
|
||||
boolean positionMatches = Math.abs(storedPosition[0] - oldVertexObj.position.x) < epsilon &&
|
||||
Math.abs(storedPosition[1] - oldVertexObj.position.y) < epsilon;
|
||||
if (positionMatches) {
|
||||
logger.debug("在{}关键帧中找到原来匹配的顶点(ID:{})并执行原地更新。", existingParameter.keyframe().get(j), oldVertexObj.getName());
|
||||
Map<String, Object> newVertexUpdatePayload = Map.of(
|
||||
"id", newVertexObj.getName(),
|
||||
"Vertex", new float[]{newVertexObj.position.x, newVertexObj.position.y}
|
||||
);
|
||||
List<Object> newValues = new ArrayList<>(values);
|
||||
newValues.set(j, newVertexUpdatePayload);
|
||||
Parameter updatedParameter = new Parameter(
|
||||
existingParameter.modelPart(),
|
||||
new ArrayList<>(existingParameter.animationParameter()),
|
||||
new ArrayList<>(existingParameter.paramId()),
|
||||
newValues,
|
||||
new ArrayList<>(existingParameter.keyframe()),
|
||||
new ArrayList<>(existingParameter.isKeyframe())
|
||||
);
|
||||
oldValues.set(i, updatedParameter);
|
||||
updatedExisting = true;
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updatedExisting) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static ParametersManagement getInstance(ParametersPanel parametersPanel) {
|
||||
String managementFilePath = parametersPanel.getRenderPanel().getGlContextManager().getModelPath() + ".data";
|
||||
File managementFile = new File(managementFilePath);
|
||||
@@ -137,16 +228,15 @@ public class ParametersManagement {
|
||||
* @param paramId 参数id
|
||||
* @param value 最终值
|
||||
*/
|
||||
public void broadcast(ModelPart modelPart, String paramId, Object value) {
|
||||
public void broadcast(ModelPart modelPart, String paramId, Object value, Float specifiedKeyframe) {
|
||||
if (getSelectParameter() == null) {
|
||||
return;
|
||||
}
|
||||
AnimationParameter currentAnimParam = getSelectParameter();
|
||||
Float currentKeyframe = getSelectedKeyframe(false);
|
||||
if (currentKeyframe == null) {
|
||||
if (specifiedKeyframe == null) {
|
||||
return;
|
||||
}
|
||||
boolean isKeyframe = currentAnimParam.getKeyframes().contains(currentKeyframe);
|
||||
boolean isKeyframe = currentAnimParam.getKeyframes().contains(specifiedKeyframe);
|
||||
String newId = null;
|
||||
if (paramId.equals("meshVertices") && value instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -164,13 +254,15 @@ public class ParametersManagement {
|
||||
List<Object> newValues = new ArrayList<>(existingParameter.value());
|
||||
List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe());
|
||||
List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe());
|
||||
|
||||
int existingIndex = -1;
|
||||
for (int j = 0; j < newKeyframes.size(); j++) {
|
||||
boolean keyframeMatches = Objects.equals(newKeyframes.get(j), currentKeyframe);
|
||||
boolean keyframeMatches = Objects.equals(newKeyframes.get(j), specifiedKeyframe);
|
||||
boolean paramIdMatches = paramId.equals(newParamIds.get(j));
|
||||
AnimationParameter recordAnimParam = newAnimationParameters.get(j);
|
||||
boolean animParamMatches = recordAnimParam != null && recordAnimParam.equals(currentAnimParam);
|
||||
boolean idMatches = true;
|
||||
|
||||
if (paramIdMatches && paramId.equals("meshVertices")) {
|
||||
Object oldValue = newValues.get(j);
|
||||
if (oldValue instanceof Map) {
|
||||
@@ -183,36 +275,46 @@ public class ParametersManagement {
|
||||
idMatches = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyframeMatches && paramIdMatches && animParamMatches && idMatches) {
|
||||
existingIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIndex != -1) {
|
||||
newValues.set(existingIndex, value);
|
||||
} else {
|
||||
newAnimationParameters.add(currentAnimParam);
|
||||
newParamIds.add(paramId);
|
||||
newValues.add(value);
|
||||
newKeyframes.add(currentKeyframe);
|
||||
newKeyframes.add(specifiedKeyframe);
|
||||
newIsKeyframes.add(isKeyframe);
|
||||
}
|
||||
|
||||
Parameter updatedParameter = new Parameter(modelPart, newAnimationParameters, newParamIds, newValues, newKeyframes, newIsKeyframes);
|
||||
oldValues.set(i, updatedParameter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到现有的参数记录,创建新的
|
||||
Parameter parameter = new Parameter(
|
||||
modelPart,
|
||||
Collections.singletonList(currentAnimParam),
|
||||
Collections.singletonList(paramId),
|
||||
Collections.singletonList(value),
|
||||
Collections.singletonList(currentKeyframe),
|
||||
Collections.singletonList(specifiedKeyframe),
|
||||
Collections.singletonList(isKeyframe)
|
||||
);
|
||||
oldValues.add(parameter);
|
||||
}
|
||||
|
||||
public void broadcast(ModelPart modelPart, String paramId, Object value) {
|
||||
Float currentKeyframe = getSelectedKeyframe(false);
|
||||
broadcast(modelPart, paramId, value, currentKeyframe);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除特定参数
|
||||
* @param modelPart 部件
|
||||
@@ -321,7 +423,7 @@ public class ParametersManagement {
|
||||
}
|
||||
sb.append(String.format("{ID=%s, V=%s, KF=%s, IsKF=%b}",
|
||||
id,
|
||||
String.valueOf(val),
|
||||
val,
|
||||
kf != null ? String.valueOf(kf) : "null",
|
||||
isKf));
|
||||
}
|
||||
|
||||
@@ -1254,38 +1254,8 @@ public class SelectionTool extends Tool {
|
||||
updateMeshVertices(hoveredMesh);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void updateMeshVertices(Mesh2D mesh) {
|
||||
if (mesh == null){
|
||||
return;
|
||||
}
|
||||
ParametersManagement.Parameter param = renderPanel.getParametersManagement().getValue(mesh.getModelPart(), "meshVertices");
|
||||
if (param == null || param.value().isEmpty() || param.keyframe().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Object> paramValue = param.value();
|
||||
for (Object keyframeDataObject : paramValue) {
|
||||
if (!(keyframeDataObject instanceof Map)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> keyframeData = (Map<String, Object>) keyframeDataObject;
|
||||
Object idObject = keyframeData.get("id");
|
||||
if (!(idObject instanceof String vertexIdInMap)) {
|
||||
continue;
|
||||
}
|
||||
for (Vertex vertex : mesh.getActiveVertexList()) {
|
||||
if (vertex == null || vertex.getName() == null) {
|
||||
continue;
|
||||
}
|
||||
if (vertexIdInMap.equals(vertex.getName())) {
|
||||
Map<String, Object> vertexUpdatePayload = Map.of(
|
||||
"id", vertex.getName(),
|
||||
"Vertex", new float[]{vertex.position.x, vertex.position.y}
|
||||
);
|
||||
renderPanel.getParametersManagement().broadcast(mesh.getModelPart(), "meshVertices", vertexUpdatePayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1542,13 +1542,11 @@ public class Mesh2D {
|
||||
|
||||
// 选中框绘制(需要切换到固色 shader)
|
||||
if (selected && !isRenderVertices) {
|
||||
RenderSystem.pushState();
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
RenderSystem.pushState();
|
||||
try {
|
||||
setSolidShader(modelMatrix);
|
||||
|
||||
if (isInMultiSelection()) {
|
||||
drawMultiSelectionBox();
|
||||
} else {
|
||||
@@ -1563,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1667,7 +1675,7 @@ public class Mesh2D {
|
||||
|
||||
private void drawSelectBox() {
|
||||
BoundingBox bounds = getBounds();
|
||||
MultiSelectionBoxRenderer.drawSelectBox(bounds, pivot);
|
||||
MultiSelectionBoxRenderer.drawSelectBox(bounds, pivot, ModelRender.getCamera().getZoom());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1776,7 +1784,7 @@ public class Mesh2D {
|
||||
*/
|
||||
private void drawMultiSelectionBox() {
|
||||
BoundingBox multiBounds = getMultiSelectionBounds();
|
||||
MultiSelectionBoxRenderer.drawMultiSelectionBox(multiBounds);
|
||||
MultiSelectionBoxRenderer.drawMultiSelectionBox(multiBounds,ModelRender.getCamera().getZoom());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1608,9 +1608,18 @@ public class ModelPart {
|
||||
for (Mesh2D mesh : meshes) {
|
||||
if (mesh == null) continue;
|
||||
for (Vertex vertex : mesh.getActiveVertexList()) {
|
||||
Vertex oldVertexCopy = vertex.copy();
|
||||
Vector2f localPoint = vertex.originalPosition;
|
||||
Vector2f worldPoint = Matrix3fUtils.transformPoint(this.worldTransform, localPoint);
|
||||
vertex.position.set(worldPoint);
|
||||
Map<String, Object> eventPayload = new HashMap<>();
|
||||
eventPayload.put("caller", this);
|
||||
eventPayload.put("oldVertex", oldVertexCopy);
|
||||
eventPayload.put("newVertex", vertex);
|
||||
for (ModelEvent event : events) {
|
||||
if (event != null)
|
||||
event.trigger("vertex", eventPayload);
|
||||
}
|
||||
}
|
||||
mesh.markDirty();
|
||||
}
|
||||
|
||||
@@ -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