package com.chuangzhou.vivid2D.render;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.util.LightSource;
import com.chuangzhou.vivid2D.render.model.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
import com.chuangzhou.vivid2D.render.systems.Camera;
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.CompleteShader;
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.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* vivid2D 模型完整渲染系统
*
*
该系统提供了完整的 vivid2D 模型加载、渲染和显示功能,支持多种渲染模式和效果:
*
*
* - 基础模型渲染
* - 光照效果渲染
* - 纹理贴图渲染
* - 模型加载与解析
*
*
* 使用示例:
*
* - {@link com.chuangzhou.vivid2D.test.ModelLoadTest} - 模型加载测试
* - {@link com.chuangzhou.vivid2D.test.ModelRenderLightingTest} - 光照渲染测试
* - {@link com.chuangzhou.vivid2D.test.ModelRenderTest} - 基础渲染测试
* - {@link com.chuangzhou.vivid2D.test.ModelRenderTest2} - 进阶渲染测试
* - {@link com.chuangzhou.vivid2D.test.ModelRenderTextureTest} - 纹理渲染测试
* - {@link com.chuangzhou.vivid2D.test.ModelTest} - 基础模型测试
* - {@link com.chuangzhou.vivid2D.test.ModelTest2} - 进阶模型测试
*
*
* @author tzdwindows
* @version 1.2
* @since 2025-10-13
*/
public final class ModelRender {
/**
* 渲染系统日志记录器,用于记录渲染过程中的调试信息、错误和性能数据
*/
private static final Logger logger = LoggerFactory.getLogger(ModelRender.class);
/**
* 私有构造函数,防止外部实例化 - 这是一个工具类,只包含静态方法
*/
private ModelRender() { /* no instances */ }
// ================== 全局状态 ==================
/**
* 渲染系统初始化状态标志,确保系统只初始化一次
*
* @see #initialize()
* @see #isInitialized()
*/
private static boolean initialized = false;
/**
* 视口宽度(像素),定义渲染区域的大小
* 默认值:800像素
*
* @see #setViewport(int, int)
*/
static int viewportWidth = 800;
/**
* 视口高度(像素),定义渲染区域的大小
* 默认值:600像素
*
* @see #setViewport(int, int)
*/
static int viewportHeight = 600;
/**
* 清除颜色(RGBA),用于在每帧开始时清空颜色缓冲区
* 默认值:黑色不透明 (0.0f, 0.0f, 0.0f, 1.0f)
*
* @see RenderSystem#clearColor(float, float, float, float)
*/
private static final Vector4f CLEAR_COLOR = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
/**
* 深度测试启用标志,控制是否进行深度缓冲测试
* 在2D渲染中通常禁用以提高性能
* 默认值:false(禁用)
*
* @see RenderSystem#enableDepthTest()
* @see RenderSystem#disableDepthTest()
*/
private static final boolean enableDepthTest = false;
/**
* 混合功能启用标志,控制透明度和颜色混合
* 默认值:true(启用)
*
* @see RenderSystem#enableBlend()
* @see RenderSystem#disableBlend()
*/
private static final boolean enableBlending = true;
/**
* 最大光源数量,用于限制同时启用的光源数量
* 默认值:80
*/
private static final int MAX_LIGHTS = 80;
// ================== 着色器与资源管理 ==================
/**
* 默认着色器程序,用于大多数模型的渲染
* 包含基础的光照、纹理和变换功能
*
* @see #compileDefaultShader()
*/
private static ShaderProgram defaultProgram = null;
/**
* 网格GPU资源缓存,管理已上传到GPU的网格数据
* 键:Mesh2D对象
* 值:对应的OpenGL资源(VAO、VBO、EBO)
*
* @see MeshGLResources
*/
private static final Map meshResources = new HashMap<>();
/**
* 纹理单元分配器,用于管理多个纹理的绑定
* 确保不同的纹理绑定到正确的纹理单元
* 默认从0开始递增分配
*/
private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0);
/**
* 默认白色纹理ID,当模型没有指定纹理时使用
* 这是一个1x1的纯白色纹理,确保模型有基本的颜色显示
*
* @see #createDefaultTexture()
*/
private static int defaultTextureId = 0;
// ================== 碰撞箱渲染配置 ==================
/**
* 碰撞箱渲染开关,控制是否在场景中显示物理碰撞体的轮廓
* 调试时非常有用,可以直观看到碰撞边界
* 默认值:true(启用)
*/
public static boolean renderColliders = true;
/**
* 碰撞箱线框宽度,控制碰撞体轮廓线的粗细
* 单位:像素
* 默认值:1.0f
*/
public static float colliderLineWidth = 1.0f;
/**
* 碰撞箱颜色(RGBA),定义碰撞体轮廓的显示颜色
* 默认值:白色不透明 (1.0f, 1.0f, 1.0f, 1.0f)
*/
public static Vector4f colliderColor = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
/**
* 圆形碰撞体细分数量,控制圆形碰撞体的平滑度
* 值越高圆形越平滑,但渲染开销也越大
* 默认值:32(在性能和视觉效果间取得平衡)
*/
private static final int CIRCLE_SEGMENTS = 32;
/**
* 光源位置渲染开关,控制是否在场景中显示光源的位置
* 用点状标记显示每个启用的光源位置
* 默认值:true(启用)
*/
public static boolean renderLightPositions = true;
// ================== 摄像机状态 ==================
/**
* 默认摄像机,用于控制场景的视图和缩放
* 默认位置:(0, 0)
*/
private static final Camera camera = new Camera();
// ================== 字体管理 ==================
private static TextRenderer defaultTextRenderer = null;
private static final int FONT_BITMAP_WIDTH = 512;
private static final int FONT_BITMAP_HEIGHT = 512;
private static final int FONT_FIRST_CHAR = 32;
private static final int FONT_CHAR_COUNT = 96;
// ================== 摄像机API方法 ==================
/**
* 获取全局摄像机实例
*/
public static Camera getCamera() {
return camera;
}
/**
* 设置摄像机位置
*/
public static void setCameraPosition(float x, float y) {
camera.setPosition(x, y);
}
/**
* 设置摄像机缩放
*/
public static void setCameraZoom(float zoom) {
camera.setZoom(zoom);
}
/**
* 设置摄像机Z轴位置
*/
public static void setCameraZPosition(float z) {
camera.setZPosition(z);
}
/**
* 移动摄像机
*/
public static void moveCamera(float dx, float dy) {
camera.move(dx, dy);
}
/**
* 缩放摄像机
*/
public static void zoomCamera(float factor) {
camera.zoom(factor);
}
/**
* 重置摄像机
*/
public static void resetCamera() {
camera.reset();
}
/**
* 启用/禁用摄像机
*/
public static void setCameraEnabled(boolean enabled) {
camera.setEnabled(enabled);
}
/**
* 构建考虑摄像机变换的投影矩阵
*/
private static Matrix3f buildCameraProjection(int width, int height) {
Matrix3f m = new Matrix3f();
if (camera.isEnabled()) {
// 考虑摄像机缩放和平移
float zoom = camera.getZoom();
Vector2f pos = camera.getPosition();
m.set(
2.0f * zoom / width, 0.0f, -1.0f - (2.0f * zoom * pos.x / width),
0.0f, -2.0f * zoom / height, 1.0f + (2.0f * zoom * pos.y / height),
0.0f, 0.0f, 1.0f
);
} else {
// 原始投影矩阵
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
}
return m;
}
// ================== 内部类:MeshGLResources ==================
private static class MeshGLResources {
int vao = 0;
int vbo = 0;
int ebo = 0;
boolean initialized = false;
void dispose() {
if (ebo != 0) {
GL15.glDeleteBuffers(ebo);
ebo = 0;
}
if (vbo != 0) {
GL15.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GL30.glDeleteVertexArrays(vao);
vao = 0;
}
initialized = false;
}
}
// ================== 初始化 / 清理 ==================
public static synchronized void initialize() {
if (initialized) return;
logger.info("Initializing ModelRender...");
// 初始化渲染系统
RenderSystem.beginInitialization();
RenderSystem.initRenderThread();
logGLInfo();
setupGLState();
try {
compileDefaultShader();
// 初始化所有非默认着色器的基础信息
initNonDefaultShaders();
} catch (RuntimeException ex) {
logger.error("Failed to compile default shader: {}", ex.getMessage());
throw ex;
}
createDefaultTexture();
RenderSystem.viewport(0, 0, viewportWidth, viewportHeight);
RenderSystem.finishInitialization();
try {
// 初始化默认字体(可替换为你自己的 TTF 数据)
ByteBuffer fontData = null;
try {
fontData = RenderSystem.loadFont("FZYTK.TTF");
} catch (Exception e) {
logger.warn("Failed to load Arial.ttf, trying fallback fonts", e);
// 尝试其他字体
try {
fontData = RenderSystem.loadFont("arial.ttf");
} catch (Exception e2) {
try {
fontData = RenderSystem.loadFont("times.ttf");
} catch (Exception e3) {
logger.error("All font loading attempts failed");
}
}
}
if (fontData != null && fontData.capacity() > 0) {
defaultTextRenderer = new TextRenderer(FONT_BITMAP_WIDTH, FONT_BITMAP_HEIGHT, FONT_FIRST_CHAR, FONT_CHAR_COUNT);
RenderSystem.checkGLError("TextRenderer constructor");
defaultTextRenderer.initialize(fontData, 20.0f);
RenderSystem.checkGLError("defaultTextRenderer initialization");
if (!defaultTextRenderer.isInitialized()) {
logger.error("TextRenderer failed to initialize properly");
}
} else {
logger.error("No valid font data available for text rendering");
}
} catch (Exception e) {
logger.warn("Failed to initialize default text renderer", e);
}
initialized = true;
logger.info("ModelRender initialized successfully");
}
/**
* 初始化所有非默认着色器的基础信息(顶点坐标等)
*/
private static void initNonDefaultShaders() {
List shaderList = ShaderManagement.getShaderList();
if (shaderList == null || shaderList.isEmpty()) {
logger.info("No shaders found to initialize");
return;
}
int nonDefaultCount = 0;
for (CompleteShader shader : shaderList) {
// 跳过默认着色器,只初始化非默认的
if (shader.isDefaultShader()) {
continue;
}
try {
// 获取着色器程序
ShaderProgram program = ShaderManagement.getShaderProgram(shader.getShaderName());
if (program == null) {
logger.warn("Shader program not found for: {}", shader.getShaderName());
continue;
}
// 设置着色器的基础uniforms(主要是顶点坐标相关的)
initShaderBasicUniforms(program, shader);
nonDefaultCount++;
logger.debug("Initialized non-default shader: {}", shader.getShaderName());
} catch (Exception e) {
logger.error("Failed to initialize non-default shader: {}", shader.getShaderName(), e);
}
}
logger.info("Initialized {} non-default shaders", nonDefaultCount);
}
/**
* 初始化着色器的基础uniforms(顶点坐标相关)
*/
private static void initShaderBasicUniforms(ShaderProgram program, CompleteShader shader) {
program.use();
try {
// 设置基础的变换矩阵为单位矩阵
setUniformMatrix3(program, "uModelMatrix", new Matrix3f().identity());
setUniformMatrix3(program, "uViewMatrix", new Matrix3f().identity());
// 设置投影矩阵(使用当前视口尺寸)
Matrix3f projection = buildOrthoProjection(viewportWidth, viewportHeight);
setUniformMatrix3(program, "uProjectionMatrix", projection);
// 设置基础颜色为白色
setUniformVec4Internal(program, "uColor", new Vector4f(1.0f, 1.0f, 1.0f, 1.0f));
// 设置基础不透明度
setUniformFloatInternal(program, "uOpacity", 1.0f);
// 设置纹理单元(如果有纹理的话)
setUniformIntInternal(program, "uTexture", 0);
RenderSystem.checkGLError("initShaderBasicUniforms_" + shader.getShaderName());
} finally {
program.stop();
}
}
private static void logGLInfo() {
RenderSystem.logDetailedGLInfo();
}
private static void uploadLightsToShader(ShaderProgram sp, Model2D model) {
List lights = model.getLights();
int idx = 0;
for (int i = 0; i < lights.size() && idx < MAX_LIGHTS; i++) {
com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i);
if (!l.isEnabled()) continue;
// 基础属性
setUniformVec2Internal(sp, "uLightsPos[" + idx + "]", l.isAmbient() ? new org.joml.Vector2f(0f, 0f) : l.getPosition());
setUniformVec3Internal(sp, "uLightsColor[" + idx + "]", l.getColor());
setUniformFloatInternal(sp, "uLightsIntensity[" + idx + "]", l.getIntensity());
setUniformIntInternal(sp, "uLightsIsAmbient[" + idx + "]", l.isAmbient() ? 1 : 0);
// 辉光相关(如果没有被设置也安全地上传默认值)
setUniformIntInternal(sp, "uLightsIsGlow[" + idx + "]", l.isGlow() ? 1 : 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + idx + "]", l.getGlowDirection() != null ? l.getGlowDirection() : new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + idx + "]", l.getGlowIntensity());
setUniformFloatInternal(sp, "uLightsGlowRadius[" + idx + "]", l.getGlowRadius());
setUniformFloatInternal(sp, "uLightsGlowAmount[" + idx + "]", l.getGlowAmount());
idx++;
}
// 上传实际有效光源数量
setUniformIntInternal(sp, "uLightCount", idx);
// 禁用剩余槽位(确保 shader 中不会读取到垃圾值)
for (int i = idx; i < MAX_LIGHTS; i++) {
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f));
// 关闭辉光槽
setUniformIntInternal(sp, "uLightsIsGlow[" + i + "]", 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + i + "]", new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowRadius[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowAmount[" + i + "]", 0f);
}
}
private static void setupGLState() {
RenderSystem.checkGLError("setupGLState_start");
RenderSystem.clearColor(CLEAR_COLOR.x, CLEAR_COLOR.y, CLEAR_COLOR.z, CLEAR_COLOR.w);
RenderSystem.checkGLError("after_clearColor");
if (enableBlending) {
RenderSystem.enableBlend();
RenderSystem.checkGLError("after_enableBlend");
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.checkGLError("after_blendFunc");
} else {
RenderSystem.disableBlend();
RenderSystem.checkGLError("after_disableBlend");
}
if (enableDepthTest) {
RenderSystem.enableDepthTest();
RenderSystem.checkGLError("after_enableDepthTest");
RenderSystem.depthFunc(GL11.GL_LEQUAL);
RenderSystem.checkGLError("after_depthFunc");
RenderSystem.depthMask(true);
RenderSystem.checkGLError("after_depthMask");
RenderSystem.clearDepth(1.0);
RenderSystem.checkGLError("after_clearDepth");
} else {
RenderSystem.disableDepthTest();
RenderSystem.checkGLError("after_disableDepthTest");
}
RenderSystem.checkGLError("after_disableCullFace");
}
private static void compileDefaultShader() {
ShaderManagement.compileAllShaders();
defaultProgram = ShaderManagement.getDefaultProgram();
if (defaultProgram == null) {
throw new RuntimeException("Failed to compile default shader: no default shader found");
}
}
private static void createDefaultTexture() {
RenderSystem.assertOnRenderThread();
defaultTextureId = RenderSystem.createDefaultTexture();
RenderSystem.checkGLError("createDefaultTexture");
}
public static synchronized void cleanup() {
if (!initialized) return;
logger.info("Cleaning up ModelRender...");
// mesh resources
for (MeshGLResources r : meshResources.values()) r.dispose();
meshResources.clear();
// 使用新的着色器管理系统清理着色器
ShaderManagement.cleanup();
defaultProgram = null;
// textures
if (defaultTextureId != 0) {
RenderSystem.deleteTextures(defaultTextureId);
defaultTextureId = 0;
}
initialized = false;
logger.info("ModelRender cleaned up");
}
// ================== 渲染流程 (已修改) ==================
public static void render(float deltaTime, Model2D model) {
if (!initialized) throw new IllegalStateException("ModelRender not initialized");
if (model == null) return;
// 确保在渲染线程
RenderSystem.assertOnRenderThread();
// 添加前置错误检查
RenderSystem.checkGLError("render_start");
// 物理系统更新
PhysicsSystem physics = model.getPhysics();
if (physics != null && physics.isEnabled()) {
physics.update(deltaTime, model);
}
model.update(deltaTime);
// 检查清除操作前的状态
RenderSystem.checkGLError("before_clear");
RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0));
RenderSystem.checkGLError("after_clear");
// 检查着色器程序
if (defaultProgram == null || defaultProgram.programId == 0) {
logger.error("Default shader program is not initialized");
return;
}
// 设置投影与视图矩阵(使用摄像机变换)
Matrix3f proj = buildCameraProjection(viewportWidth, viewportHeight);
Matrix3f view = new Matrix3f().identity();
// 1. 首先设置默认着色器
defaultProgram.use();
RenderSystem.checkGLError("after_use_default_program");
// 设置默认着色器的投影与视图
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
setUniformMatrix3(defaultProgram, "uViewMatrix", view);
RenderSystem.checkGLError("after_set_default_matrices");
// 设置摄像机Z轴位置(如果着色器支持)
setUniformFloatInternal(defaultProgram, "uCameraZ", camera.getZPosition());
RenderSystem.checkGLError("after_set_camera_z");
// 添加光源数据上传到默认着色器
uploadLightsToShader(defaultProgram, model);
RenderSystem.checkGLError("after_upload_lights");
// 2. 设置非默认着色器的顶点坐标相关uniform
setupNonDefaultShaders(proj, view);
RenderSystem.checkGLError("after_setup_non_default_shaders");
// 在渲染光源位置前检查
RenderSystem.checkGLError("before_render_light_positions");
renderLightPositions(model);
RenderSystem.checkGLError("after_render_light_positions");
// 递归渲染所有根部件(使用默认着色器)
Matrix3f identity = new Matrix3f().identity();
for (ModelPart p : model.getParts()) {
if (p.getParent() != null) continue;
renderPartRecursive(p, identity);
}
RenderSystem.checkGLError("after_render_parts");
if (renderColliders && physics != null) {
renderPhysicsColliders(physics);
RenderSystem.checkGLError("after_render_colliders");
}
//if (defaultTextRenderer != null) {
// String camInfo = String.format("Camera X: %.2f Y: %.2f Zoom: %.2f",
// camera.getPosition().x,
// camera.getPosition().y,
// camera.getZoom());
// float x = 10.0f;
// float y = viewportHeight - 30.0f;
// Vector4f color = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
// renderText(camInfo, x, y, color);
// RenderSystem.checkGLError("renderText");
//}
RenderSystem.checkGLError("render_end");
}
// ================== 缩略图渲染方法 ==================
/**
* 渲染模型缩略图(图层式渲染,不受摄像机控制)
*
* 该方法提供类似PS图层预览的缩略图渲染功能:
*
* - 固定位置和大小,不受摄像机影响
* - 自动缩放确保模型完全可见
* - 禁用复杂效果以提高性能
* - 独立的渲染状态管理
*
*
* @param model 要渲染的模型
* @param x 缩略图左上角X坐标(屏幕坐标)
* @param y 缩略图左上角Y坐标(屏幕坐标)
* @param width 缩略图宽度
* @param height 缩略图高度
*/
public static void renderThumbnail(Model2D model, float x, float y, float width, float height) {
if (!initialized) throw new IllegalStateException("ModelRender not initialized");
if (model == null) return;
RenderSystem.assertOnRenderThread();
RenderSystem.checkGLError("renderThumbnail_start");
// 保存原始状态以便恢复
boolean originalRenderColliders = renderColliders;
boolean originalRenderLightPositions = renderLightPositions;
int originalViewportWidth = viewportWidth;
int originalViewportHeight = viewportHeight;
try {
// 设置缩略图专用状态
renderColliders = false;
renderLightPositions = false;
// 设置缩略图视口(屏幕坐标)
RenderSystem.viewport((int)x, (int)y, (int)width, (int)height);
// 清除缩略图区域
RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0));
RenderSystem.checkGLError("thumbnail_after_clear");
// 简化版的模型更新(跳过物理系统)
model.update(0.016f); // 使用固定时间步长
// 计算模型边界和缩放比例
ThumbnailBounds bounds = calculateThumbnailBounds(model, width, height);
// 设置缩略图专用的正交投影(固定位置,不受摄像机影响)
Matrix3f proj = buildThumbnailProjection(width, height);
Matrix3f view = new Matrix3f().identity();
// 使用默认着色器
defaultProgram.use();
RenderSystem.checkGLError("thumbnail_after_use_program");
// 设置基础变换矩阵
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
setUniformMatrix3(defaultProgram, "uViewMatrix", view);
setUniformFloatInternal(defaultProgram, "uCameraZ", 0f); // 固定Z位置
RenderSystem.checkGLError("thumbnail_after_set_matrices");
// 简化光源:只使用环境光
setupThumbnailLighting(defaultProgram, model);
RenderSystem.checkGLError("thumbnail_after_setup_lighting");
// 应用缩放和平移确保模型完全可见
Matrix3f thumbnailTransform = new Matrix3f(
bounds.scale, 0, bounds.offsetX,
0, bounds.scale, bounds.offsetY,
0, 0, 1
);
// 递归渲染所有根部件(应用缩略图专用变换)
for (ModelPart p : model.getParts()) {
if (p.getParent() != null) continue;
renderPartForThumbnail(p, thumbnailTransform);
}
RenderSystem.checkGLError("thumbnail_after_render_parts");
} finally {
// 恢复原始状态
renderColliders = originalRenderColliders;
renderLightPositions = originalRenderLightPositions;
RenderSystem.viewport(0, 0, originalViewportWidth, originalViewportHeight);
}
RenderSystem.checkGLError("renderThumbnail_end");
}
/**
* 缩略图边界计算结果
*/
private static class ThumbnailBounds {
public float minX, maxX, minY, maxY;
public float scale;
public float offsetX, offsetY;
}
/**
* 计算模型的边界和合适的缩放比例
*/
private static ThumbnailBounds calculateThumbnailBounds(Model2D model, float thumbWidth, float thumbHeight) {
ThumbnailBounds bounds = new ThumbnailBounds();
// 初始化为极值
bounds.minX = Float.MAX_VALUE;
bounds.maxX = Float.MIN_VALUE;
bounds.minY = Float.MAX_VALUE;
bounds.maxY = Float.MIN_VALUE;
// 计算模型的世界坐标边界(递归遍历所有部件)
calculateModelBounds(model, bounds, new Matrix3f().identity());
// 如果模型没有有效边界,使用默认值
if (bounds.minX > bounds.maxX) {
bounds.minX = -50f;
bounds.maxX = 50f;
bounds.minY = -50f;
bounds.maxY = 50f;
}
// 计算模型宽度和高度
float modelWidth = bounds.maxX - bounds.minX;
float modelHeight = bounds.maxY - bounds.minY;
// 计算中心点
float centerX = (bounds.minX + bounds.maxX) * 0.5f;
float centerY = (bounds.minY + bounds.maxY) * 0.5f;
// 计算缩放比例(考虑边距)
float margin = 0.1f; // 10%边距
float scaleX = (thumbWidth * (1 - margin)) / modelWidth;
float scaleY = (thumbHeight * (1 - margin)) / modelHeight;
bounds.scale = Math.min(scaleX, scaleY);
// 计算偏移量(将模型中心对齐到缩略图中心)
bounds.offsetX = -centerX;
bounds.offsetY = -centerY;
return bounds;
}
/**
* 递归计算模型的边界
*/
private static void calculateModelBounds(Model2D model, ThumbnailBounds bounds, Matrix3f parentTransform) {
for (ModelPart part : model.getParts()) {
if (part.getParent() != null) continue; // 只处理根部件
// 计算部件的世界变换
part.updateWorldTransform(parentTransform, false);
Matrix3f worldTransform = part.getWorldTransform();
// 计算部件的边界
calculatePartBounds(part, bounds, worldTransform);
// 递归处理子部件
for (ModelPart child : part.getChildren()) {
calculateModelBoundsForPart(child, bounds, worldTransform);
}
}
}
/**
* 递归计算部件及其子部件的边界
*/
private static void calculateModelBoundsForPart(ModelPart part, ThumbnailBounds bounds, Matrix3f parentTransform) {
part.updateWorldTransform(parentTransform, false);
Matrix3f worldTransform = part.getWorldTransform();
calculatePartBounds(part, bounds, worldTransform);
for (ModelPart child : part.getChildren()) {
calculateModelBoundsForPart(child, bounds, worldTransform);
}
}
/**
* 计算单个部件的边界
*/
private static void calculatePartBounds(ModelPart part, ThumbnailBounds bounds, Matrix3f worldTransform) {
for (Mesh2D mesh : part.getMeshes()) {
if (!mesh.isVisible()) continue;
// 获取网格的顶点数据
float[] vertices = mesh.getVertices(); // 假设有这个方法获取原始顶点
if (vertices == null) continue;
// 变换顶点并更新边界
for (int i = 0; i < vertices.length; i += 3) { // 假设顶点格式:x, y, z
float x = vertices[i];
float y = vertices[i + 1];
// 应用世界变换
Vector3f transformed = new Vector3f(x, y, 1.0f);
worldTransform.transform(transformed);
// 更新边界
bounds.minX = Math.min(bounds.minX, transformed.x);
bounds.maxX = Math.max(bounds.maxX, transformed.x);
bounds.minY = Math.min(bounds.minY, transformed.y);
bounds.maxY = Math.max(bounds.maxY, transformed.y);
}
}
}
/**
* 构建缩略图专用的正交投影矩阵
*/
private static Matrix3f buildThumbnailProjection(float width, float height) {
Matrix3f m = new Matrix3f();
// 标准正交投影,不受摄像机影响
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
return m;
}
/**
* 缩略图专用的部件渲染
*/
public static void renderPartForThumbnail(ModelPart part, Matrix3f parentTransform) {
part.updateWorldTransform(parentTransform, false);
Matrix3f world = part.getWorldTransform();
setPartUniforms(defaultProgram, part);
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
for (Mesh2D mesh : part.getMeshes()) {
renderMeshForThumbnail(mesh, world);
}
for (ModelPart child : part.getChildren()) {
renderPartForThumbnail(child, world);
}
}
/**
* 缩略图专用的网格渲染
*/
private static void renderMeshForThumbnail(Mesh2D mesh, Matrix3f modelMatrix) {
if (!mesh.isVisible()) return;
Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : new Matrix3f(modelMatrix);
if (mesh.getTexture() != null) {
mesh.getTexture().bind(0);
setUniformIntInternal(defaultProgram, "uTexture", 0);
} else {
RenderSystem.bindTexture(defaultTextureId);
setUniformIntInternal(defaultProgram, "uTexture", 0);
}
setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse);
mesh.draw(defaultProgram.programId, matToUse);
RenderSystem.checkGLError("renderMeshForThumbnail");
}
/**
* 设置缩略图专用的简化光照
*/
private static void setupThumbnailLighting(ShaderProgram sp, Model2D model) {
List lights = model.getLights();
int ambientLightCount = 0;
// 查找环境光
for (int i = 0; i < lights.size() && ambientLightCount < 1; i++) {
LightSource light = lights.get(i);
if (light.isEnabled() && light.isAmbient()) {
setUniformVec2Internal(sp, "uLightsPos[0]", new Vector2f(0f, 0f));
setUniformVec3Internal(sp, "uLightsColor[0]", light.getColor());
setUniformFloatInternal(sp, "uLightsIntensity[0]", light.getIntensity());
setUniformIntInternal(sp, "uLightsIsAmbient[0]", 1);
setUniformIntInternal(sp, "uLightsIsGlow[0]", 0);
ambientLightCount++;
}
}
// 如果没有环境光,创建一个默认的环境光
if (ambientLightCount == 0) {
setUniformVec2Internal(sp, "uLightsPos[0]", new Vector2f(0f, 0f));
setUniformVec3Internal(sp, "uLightsColor[0]", new Vector3f(0.8f, 0.8f, 0.8f));
setUniformFloatInternal(sp, "uLightsIntensity[0]", 1.0f);
setUniformIntInternal(sp, "uLightsIsAmbient[0]", 1);
setUniformIntInternal(sp, "uLightsIsGlow[0]", 0);
ambientLightCount = 1;
}
setUniformIntInternal(sp, "uLightCount", ambientLightCount);
// 禁用所有其他光源槽位
for (int i = ambientLightCount; i < MAX_LIGHTS; i++) {
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
}
}
/**
* 设置所有非默认着色器的顶点坐标相关uniform
*/
private static void setupNonDefaultShaders(Matrix3f projection, Matrix3f view) {
List shaderList = ShaderManagement.getShaderList();
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);
}
}
} finally {
// 恢复之前绑定的着色器程序
if (currentProgram != 0) {
GL20.glUseProgram(currentProgram);
}
}
}
private static void renderLightPositions(Model2D model) {
if (!renderLightPositions) return;
// 设置灯泡颜色为光源的颜色
for (LightSource light : model.getLights()) {
if (!light.isEnabled()) continue;
// 使用光源的颜色来绘制灯泡
Vector4f lightColor = new Vector4f(light.getColor().x, light.getColor().y, light.getColor().z, 1.0f);
setUniformVec4Internal(defaultProgram, "uColor", lightColor);
// 绘制灯泡形状
drawLightBulb(light.getPosition(), light.getIntensity());
if (light.isAmbient()) {
drawCrossMark(light.getPosition(), light.getIntensity());
}
}
// 恢复原始颜色
setUniformVec4Internal(defaultProgram, "uColor", new Vector4f(1, 1, 1, 1));
}
/**
* 绘制简洁的灯泡形状
*
* @param position 灯泡位置
* @param intensity 光源强度,用于控制灯泡大小
*/
private static void drawLightBulb(Vector2f position, float intensity) {
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
float bulbSize = 3.0f + (intensity / 10.0f);
int segments = 16;
builder.begin(RenderSystem.DRAW_TRIANGLE_FAN, segments + 2);
builder.vertex(position.x, position.y, 0.5f, 0.5f);
for (int i = 0; i <= segments; i++) {
double angle = 2.0 * Math.PI * i / segments;
float x = position.x + bulbSize * (float) Math.cos(angle);
float y = position.y + bulbSize * (float) Math.sin(angle);
builder.vertex(x, y, 0.5f, 0.5f);
}
tesselator.end();
}
/**
* 绘制十字标记(用于环境光)
*/
private static void drawCrossMark(Vector2f position, float size) {
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
float crossSize = size * 0.8f;
// 绘制水平线
builder.begin(RenderSystem.DRAW_LINES, 2);
builder.vertex(position.x - crossSize, position.y, 0.5f, 0.5f);
builder.vertex(position.x + crossSize, position.y, 0.5f, 0.5f);
tesselator.end();
// 绘制垂直线
builder.begin(RenderSystem.DRAW_LINES, 2);
builder.vertex(position.x, position.y - crossSize, 0.5f, 0.5f);
builder.vertex(position.x, position.y + crossSize, 0.5f, 0.5f);
tesselator.end();
}
/**
* 关键修改点:在渲染前确保更新 part 的 worldTransform,
* 然后直接使用 part.getWorldTransform() 作为 uModelMatrix 传入 shader。
*/
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
// 确保 part 的 local/world 矩阵被计算(会更新 transformDirty)
part.updateWorldTransform(parentMat, false);
// 直接使用已经计算好的 worldTransform
Matrix3f world = part.getWorldTransform();
// 先设置部件相关的 uniform(opacity / blendMode / color 等)
setPartUniforms(defaultProgram, part);
// 把 world 矩阵传给 shader(uModelMatrix)
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
// 绘制本节点的所有 mesh(将 world 传入 renderMesh)
for (Mesh2D mesh : part.getMeshes()) {
renderMesh(mesh, world);
}
// 递归渲染子节点,继续传入当前 world 作为子节点的 parent
for (ModelPart child : part.getChildren()) {
renderPartRecursive(child, world);
}
}
private static void renderMesh(Mesh2D mesh, Matrix3f modelMatrix) {
if (!mesh.isVisible()) return;
// 如果 mesh 已经被烘焙到世界坐标,则传 identity 矩阵给 shader(防止重复变换)
Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : new Matrix3f(modelMatrix);
// 手动应用摄像机偏移
Vector2f offset = getCameraOffset();
matToUse.m20(matToUse.m20() - offset.x);
matToUse.m21(matToUse.m21() - offset.y);
// 设置纹理相关的uniform
if (mesh.getTexture() != null) {
mesh.getTexture().bind(0); // 绑定到纹理单元0
setUniformIntInternal(defaultProgram, "uTexture", 0);
} else {
// 使用默认白色纹理
RenderSystem.bindTexture(defaultTextureId);
setUniformIntInternal(defaultProgram, "uTexture", 0);
}
// 将模型矩阵设置为当前 mesh 使用的矩阵(shader 内名为 uModelMatrix)
setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse);
// 调用 Mesh2D 的 draw 方法,传入当前使用的着色器程序和变换矩阵
mesh.draw(defaultProgram.programId, matToUse);
RenderSystem.checkGLError("renderMesh");
}
// ================== 渲染碰撞箱相关实现 ==================
private static void renderPhysicsColliders(PhysicsSystem physics) {
if (physics == null) {
logger.warn("renderPhysicsColliders: physics system is null");
return;
}
// 设置渲染状态
RenderSystem.checkGLError("before_set_line_width");
RenderSystem.lineWidth(colliderLineWidth);
RenderSystem.checkGLError("after_set_line_width");
RenderSystem.activeTexture(RenderSystem.GL_TEXTURE0);
RenderSystem.bindTexture(defaultTextureId);
RenderSystem.checkGLError("after_bind_texture");
setUniformIntInternal(defaultProgram, "uTexture", 0);
setUniformVec4Internal(defaultProgram, "uColor", colliderColor);
setUniformFloatInternal(defaultProgram, "uOpacity", 1.0f);
setUniformIntInternal(defaultProgram, "uBlendMode", 0);
setUniformIntInternal(defaultProgram, "uDebugMode", 0);
RenderSystem.checkGLError("after_set_uniforms");
// 使用单位矩阵作为 model(碰撞体顶点按世界坐标提供)
setUniformMatrix3(defaultProgram, "uModelMatrix", new Matrix3f().identity());
RenderSystem.checkGLError("after_set_model_matrix");
List colliders = physics.getColliders();
if (colliders == null || colliders.isEmpty()) {
logger.debug("No colliders to render");
return;
}
int enabledColliders = 0;
for (PhysicsSystem.PhysicsCollider collider : colliders) {
if (collider == null || !collider.isEnabled()) continue;
RenderSystem.checkGLError("before_render_collider_" + enabledColliders);
if (collider instanceof PhysicsSystem.CircleCollider c) {
if (c.getCenter() != null && c.getRadius() > 0) {
drawCircleColliderWire(c.getCenter(), c.getRadius());
enabledColliders++;
} else {
logger.warn("Invalid CircleCollider: center={}, radius={}", c.getCenter(), c.getRadius());
}
} else if (collider instanceof PhysicsSystem.RectangleCollider r) {
if (r.getCenter() != null && r.getWidth() > 0 && r.getHeight() > 0) {
drawRectangleColliderWire(r.getCenter(), r.getWidth(), r.getHeight());
enabledColliders++;
} else {
logger.warn("Invalid RectangleCollider: center={}, width={}, height={}",
r.getCenter(), r.getWidth(), r.getHeight());
}
} else {
logger.warn("Unknown collider type: {}", collider.getClass().getSimpleName());
}
RenderSystem.checkGLError("after_render_collider_" + enabledColliders);
}
logger.debug("Rendered {} enabled colliders", enabledColliders);
// 恢复默认线宽
RenderSystem.lineWidth(1.0f);
RenderSystem.checkGLError("after_reset_line_width");
}
/**
* 绘制圆形碰撞框(线框)
* 使用临时 VAO/VBO,每帧创建并删除(简单实现)
*/
private static void drawCircleColliderWire(Vector2f center, float radius) {
int segments = Math.max(8, CIRCLE_SEGMENTS);
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
builder.begin(RenderSystem.DRAW_LINE_LOOP, segments);
for (int i = 0; i < segments; i++) {
double ang = 2.0 * Math.PI * i / segments;
float x = center.x + radius * (float) Math.cos(ang);
float y = center.y + radius * (float) Math.sin(ang);
builder.vertex(x, y, 0.5f, 0.5f);
}
tesselator.end();
}
/**
* 绘制矩形碰撞框(线框)
*/
private static void drawRectangleColliderWire(Vector2f center, float width, float height) {
float halfW = width / 2.0f;
float halfH = height / 2.0f;
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
builder.begin(RenderSystem.DRAW_LINE_LOOP, 4);
builder.vertex(center.x - halfW, center.y - halfH, 0.5f, 0.5f);
builder.vertex(center.x + halfW, center.y - halfH, 0.5f, 0.5f);
builder.vertex(center.x + halfW, center.y + halfH, 0.5f, 0.5f);
builder.vertex(center.x - halfW, center.y + halfH, 0.5f, 0.5f);
tesselator.end();
}
// ================== uniform 设置辅助(内部使用,确保 program 已绑定) ==================
private static void setUniformIntInternal(ShaderProgram sp, String name, int value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform1i(loc, value);
}
private static void setUniformVec3Internal(ShaderProgram sp, String name, org.joml.Vector3f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform3f(loc, vec);
}
private static void setUniformVec2Internal(ShaderProgram sp, String name, org.joml.Vector2f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform2f(loc, vec);
}
private static void setUniformFloatInternal(ShaderProgram sp, String name, float value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform1f(loc, value);
}
private static void setUniformVec4Internal(ShaderProgram sp, String name, org.joml.Vector4f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform4f(loc, vec);
}
private static void setUniformMatrix3(ShaderProgram sp, String name, org.joml.Matrix3f m) {
int loc = sp.getUniformLocation(name);
if (loc == -1) return;
RenderSystem.uniformMatrix3(loc, m);
}
// ================== 部件属性 ==================
private static void setPartUniforms(ShaderProgram sp, ModelPart part) {
setUniformFloatInternal(sp, "uOpacity", part.getOpacity());
int blend = 0;
ModelPart.BlendMode bm = part.getBlendMode();
if (bm != null) {
switch (bm) {
case ADDITIVE:
blend = 1;
break;
case MULTIPLY:
blend = 2;
break;
case SCREEN:
blend = 3;
break;
case NORMAL:
default:
blend = 0;
}
} else {
blend = 0;
}
setUniformIntInternal(sp, "uBlendMode", blend);
// 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性
setUniformVec4Internal(sp, "uColor", new Vector4f(1, 1, 1, 1));
}
public static TextRenderer getTextRenderer() {
return defaultTextRenderer;
}
// ================== 工具 ==================
private static Matrix3f buildOrthoProjection(int width, int height) {
Matrix3f m = new Matrix3f();
// 这个投影把屏幕像素坐标(x in [0,width], y in [0,height])映射到 NDC [-1,1]x[1,-1]
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
return m;
}
/**
* 渲染文字
*
* @param text 文字内容
* @param x 世界坐标 X
* @param y 世界坐标 Y ,反转的
* @param color RGBA 颜色
*/
public static void renderText(String text, float x, float y, Vector4f color) {
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);
}
/**
* 获取默认摄像机与当前摄像机之间的偏移量
*
* @return Vector2f 偏移向量 (dx, dy)
*/
public static Vector2f getCameraOffset() {
float width = viewportWidth;
float height = viewportHeight;
float zoom = camera.getZoom();
Vector2f pos = camera.getPosition();
float tx = -1.0f - (2.0f * zoom * pos.x / width);
float ty = 1.0f + (2.0f * zoom * pos.y / height);
float tx0 = -1.0f;
float ty0 = 1.0f;
float offsetX = tx - tx0;
float offsetY = ty - ty0;
offsetX = -offsetX * width / 2.0f / zoom;
offsetY = offsetY * height / 2.0f / zoom;
return new Vector2f(offsetX, offsetY);
}
public static void setViewport(int width, int height) {
viewportWidth = Math.max(1, width);
viewportHeight = Math.max(1, height);
RenderSystem.viewport(0, 0, viewportWidth, viewportHeight);
}
// ================== 辅助:外部获取状态 ==================
public static boolean isInitialized() {
return initialized;
}
public static int getLoadedMeshCount() {
return meshResources.size();
}
}