diff --git a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java index 258a3d0..2c24ef8 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java @@ -317,33 +317,58 @@ public final class ModelRender { checkGLError("render"); } + /** + * 关键修改点:在渲染前确保更新 part 的 worldTransform, + * 然后直接使用 part.getWorldTransform() 作为 uModelMatrix 传入 shader。 + */ private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) { - Matrix3f local = part.getLocalTransform(); - Matrix3f world = new Matrix3f(parentMat).mul(local); // world = parent * local + // 确保 part 的 local/world 矩阵被计算(会更新 transformDirty) + part.updateWorldTransform(parentMat, false); - // 从 world 矩阵取世界坐标 - //float worldX = world.m02; - //float worldY = world.m12; - //System.out.println("Rendering part: " + part.getName() + " at world position: " + worldX + ", " + worldY); - // 传入 shader - setUniformMatrix3(defaultProgram, "uModelMatrix", world); + // 直接使用已经计算好的 worldTransform + Matrix3f world = part.getWorldTransform(); + + // 先设置部件相关的 uniform(opacity / blendMode / color 等) setPartUniforms(defaultProgram, part); + // 把 world 矩阵传给 shader(兼容 uModelMatrix 和 可能的 uModel) + setUniformMatrix3(defaultProgram, "uModelMatrix", world); + setUniformMatrix3(defaultProgram, "uModel", world); + + // 绘制本节点的所有 mesh(将 world 传入 renderMesh) for (Mesh2D mesh : part.getMeshes()) { - renderMesh(mesh); + renderMesh(mesh, world); } + // 递归渲染子节点,继续传入当前 world 作为子节点的 parent for (ModelPart child : part.getChildren()) { renderPartRecursive(child, world); } } + private static void renderMesh(Mesh2D mesh, Matrix3f modelMatrix) { + // 使用默认 shader(保证 shader 已被 use) + defaultProgram.use(); + + // 如果 mesh 已经被烘焙到世界坐标,则传 identity 矩阵给 shader(防止重复变换) + Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : modelMatrix; + + // 确保 shader 中的矩阵 uniform 已更新(再次设置以防遗漏) + setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse); + setUniformMatrix3(defaultProgram, "uModel", matToUse); + + // 调用 Mesh2D 的 draw 重载(传 program id 与实际矩阵) + try { + mesh.draw(defaultProgram.programId, matToUse); + } catch (AbstractMethodError | NoSuchMethodError e) { + // 回退:仍然兼容旧的无参 draw(在这种情况下 shader 的 uModelMatrix 已经被设置) + mesh.draw(); + } - private static void renderMesh(Mesh2D mesh) { - mesh.draw(); checkGLError("renderMesh"); } + private static int getGLDrawMode(int meshDrawMode) { switch (meshDrawMode) { case Mesh2D.POINTS: return GL11.GL_POINTS; @@ -357,78 +382,7 @@ public final class ModelRender { } // ================== 上传数据 ==================(被弃用) - //private static void uploadMeshData(Mesh2D mesh, MeshGLResources res) { - // System.out.println("Uploading mesh data: " + mesh.getName()); -// - // res.vao = GL30.glGenVertexArrays(); - // GL30.glBindVertexArray(res.vao); -// - // res.vbo = GL15.glGenBuffers(); - // GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, res.vbo); -// - // float[] verts = mesh.getVertices(); - // float[] uvs = mesh.getUVs(); - // int vertexCount = mesh.getVertexCount(); - // if (verts == null || verts.length == 0) throw new IllegalStateException("Mesh has no vertices: " + mesh.getName()); -// - // FloatBuffer inter = MemoryUtil.memAllocFloat(vertexCount * 4); -// - // // ========== 添加调试输出 ========== - // System.out.println("=== Vertex data debug output ==="); - // System.out.println("Grid name: " + mesh.getName()); - // System.out.println("Number of vertices: " + vertexCount); - // System.out.println("Vertex coordinates (x, y):"); -// - // for (int i = 0; i < vertexCount; i++) { - // float x = verts[i*2]; - // float y = verts[i*2+1]; - // float u = uvs[i*2]; - // float v = uvs[i*2+1]; -// - // // 输出每个顶点的坐标和UV - // System.out.printf("vertex %d: location(%.3f, %.3f), UV(%.3f, %.3f)%n", - // i, x, y, u, v); -// - // inter.put(x); - // inter.put(y); - // inter.put(u); - // inter.put(v); - // } - // System.out.println("=== Vertex data output is over ==="); - // // ========== 调试输出结束 ========== -// - // inter.flip(); - // GL15.glBufferData(GL15.GL_ARRAY_BUFFER, inter, GL15.GL_STATIC_DRAW); - // MemoryUtil.memFree(inter); -// - // // 设置 attribute(位置 / uv),layout 已在 shader 中固定 - // int stride = 4 * Float.BYTES; - // GL20.glEnableVertexAttribArray(0); - // GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, stride, 0); - // GL20.glEnableVertexAttribArray(1); - // GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, stride, 2 * Float.BYTES); -// - // int[] indices = mesh.getIndices(); - // if (indices != null && indices.length > 0) { - // res.ebo = GL15.glGenBuffers(); - // GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, res.ebo); - // IntBuffer ib = MemoryUtil.memAllocInt(indices.length); - // ib.put(indices).flip(); - // GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ib, GL15.GL_STATIC_DRAW); - // MemoryUtil.memFree(ib); - // res.vertexCount = indices.length; // drawElements 使用 count - // } else { - // res.vertexCount = vertexCount; - // } -// - // // 不解绑 ELEMENT_ARRAY_BUFFER(它属于 VAO),解绑 ARRAY_BUFFER - // GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); - // GL30.glBindVertexArray(0); -// - // res.initialized = true; - // checkGLError("uploadMeshData"); - // System.out.println("Uploaded mesh: " + mesh.getName() + " (v=" + vertexCount + ")"); - //} + // ================== uniform 设置辅助(内部使用,确保 program 已绑定) ================== private static void setUniformIntInternal(ShaderProgram sp, String name, int value) { @@ -480,11 +434,16 @@ public final class ModelRender { private static void setPartUniforms(ShaderProgram sp, ModelPart part) { setUniformFloatInternal(sp, "uOpacity", part.getOpacity()); int blend = 0; - switch (part.getBlendMode()) { - case ADDITIVE: blend = 1; break; - case MULTIPLY: blend = 2; break; - case SCREEN: blend = 3; break; - case NORMAL: default: 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 属性 diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java index 7f5dfee..b3ba248 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/ModelPart.java @@ -72,6 +72,8 @@ public class ModelPart { this.boundsDirty = true; updateLocalTransform(); + // 初始时 worldTransform = localTransform(无父节点时) + recomputeWorldTransformRecursive(); } // ==================== 层级管理 ==================== @@ -88,7 +90,9 @@ public class ModelPart { } children.add(child); child.parent = this; - markTransformDirty(); + child.markTransformDirty(); + // 确保子节点的 worldTransform 立即更新 + child.recomputeWorldTransformRecursive(); } /** @@ -98,7 +102,8 @@ public class ModelPart { boolean removed = children.remove(child); if (removed) { child.parent = null; - markTransformDirty(); + child.markTransformDirty(); + child.recomputeWorldTransformRecursive(); } return removed; } @@ -146,7 +151,7 @@ public class ModelPart { // ==================== 变换系统 ==================== /** - * 更新世界变换 + * 更新世界变换(保留旧方法以兼容) */ public void updateWorldTransform(Matrix3f parentTransform, boolean recursive) { // 如果需要更新局部变换 @@ -169,6 +174,21 @@ public class ModelPart { transformDirty = false; } + public void draw(int shaderProgram, org.joml.Matrix3f parentTransform) { + // 先确保 worldTransform 是最新的 + updateWorldTransform(parentTransform, false); + + // 绘制本节点的所有 mesh(将 worldTransform 作为 model 矩阵传入) + for (Mesh2D mesh : meshes) { + mesh.draw(shaderProgram, worldTransform); + } + + // 递归绘制子节点 + for (ModelPart child : children) { + child.draw(shaderProgram, worldTransform); + } + } + // 更新局部矩阵 private void updateLocalTransform() { float cos = (float)Math.cos(rotation); @@ -189,15 +209,36 @@ public class ModelPart { ); } + /** + * 立即重新计算本节点的 worldTransform(并递归到子节点) + */ + private void recomputeWorldTransformRecursive() { + if (transformDirty) { + updateLocalTransform(); + } + if (parent != null) { + // parent.worldTransform 已经是最新(假设父节点正确维护) + parent.worldTransform.mul(localTransform, worldTransform); + } else { + worldTransform.set(localTransform); + } - // 打印世界坐标 - public void printWorldPosition() { - float worldX = localTransform.m02; - float worldY = localTransform.m12; - System.out.println("World position: " + worldX + ", " + worldY); + // 递归更新子节点 + for (ModelPart child : children) { + child.recomputeWorldTransformRecursive(); + } + + boundsDirty = true; + transformDirty = false; } + // 打印世界坐标(修正为使用 worldTransform) + public void printWorldPosition() { + float worldX = worldTransform.m02(); + float worldY = worldTransform.m12(); + System.out.println("World position: " + worldX + ", " + worldY); + } /** * 标记变换需要更新 @@ -215,11 +256,15 @@ public class ModelPart { public void setPosition(float x, float y) { position.set(x, y); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } public void setPosition(Vector2f position) { this.position.set(position); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } /** @@ -228,11 +273,15 @@ public class ModelPart { public void translate(float dx, float dy) { position.add(dx, dy); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } public void translate(Vector2f delta) { position.add(delta); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } /** @@ -241,6 +290,8 @@ public class ModelPart { public void setRotation(float radians) { this.rotation = radians; markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } /** @@ -249,6 +300,8 @@ public class ModelPart { public void rotate(float deltaRadians) { this.rotation += deltaRadians; markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } /** @@ -257,16 +310,22 @@ public class ModelPart { public void setScale(float sx, float sy) { scale.set(sx, sy); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } public void setScale(float uniformScale) { scale.set(uniformScale, uniformScale); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } public void setScale(Vector2f scale) { this.scale.set(scale); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } /** @@ -275,6 +334,8 @@ public class ModelPart { public void scale(float sx, float sy) { scale.mul(sx, sy); markTransformDirty(); + updateLocalTransform(); + recomputeWorldTransformRecursive(); } // ==================== 网格管理 ==================== @@ -283,6 +344,18 @@ public class ModelPart { * 添加网格 */ public void addMesh(Mesh2D mesh) { + if (mesh == null) return; + + // 确保本节点的 worldTransform 是最新的(会递归更新子节点) + recomputeWorldTransformRecursive(); + + // 将 mesh 的每个顶点从本地空间变换到世界空间(烘焙) + int vc = mesh.getVertexCount(); + for (int i = 0; i < vc; i++) { + org.joml.Vector2f local = mesh.getVertex(i); + org.joml.Vector2f worldPt = Matrix3fUtils.transformPoint(this.worldTransform, local); + mesh.setVertex(i, worldPt.x, worldPt.y); + } meshes.add(mesh); boundsDirty = true; } @@ -517,4 +590,4 @@ public class ModelPart { ", meshes=" + meshes.size() + '}'; } -} \ No newline at end of file +} diff --git a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java index 7ee0d08..d7039a2 100644 --- a/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java +++ b/src/main/java/com/chuangzhou/vivid2D/render/model/util/Mesh2D.java @@ -10,8 +10,7 @@ import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; import org.lwjgl.system.MemoryUtil; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; + /** * 2D网格类,用于存储和管理2D模型的几何数据 * 支持顶点、UV坐标、索引和变形操作 @@ -40,6 +39,7 @@ public class Mesh2D { private boolean dirty = true; // 数据是否已修改 private BoundingBox bounds; private boolean boundsDirty = true; + private boolean bakedToWorld = false; // ==================== 常量 ==================== public static final int POINTS = 0; @@ -178,8 +178,7 @@ public class Mesh2D { if (index < 0 || index >= getVertexCount()) { throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index); } - int baseIndex = index * 2; - return dest.set(vertices[baseIndex], vertices[baseIndex + 1]); + return dest.set(getX(index), getY(index)); } public Vector2f getVertex(int index) { @@ -308,7 +307,7 @@ public class Mesh2D { * 获取顶点缓冲区数据 */ public FloatBuffer getVertexBuffer(FloatBuffer buffer) { - if (buffer == null || buffer.capacity() < vertices.length) { + if (buffer == null || buffer.remaining() < vertices.length) { throw new IllegalArgumentException("Buffer is null or too small"); } buffer.clear(); @@ -330,6 +329,20 @@ public class Mesh2D { return buffer; } + public float getX(int index) { + if (index < 0 || index >= getVertexCount()) { + throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index); + } + return vertices[index * 2]; + } + + public float getY(int index) { + if (index < 0 || index >= getVertexCount()) { + throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index); + } + return vertices[index * 2 + 1]; + } + /** * 获取索引缓冲区数据 */ @@ -350,17 +363,18 @@ public class Mesh2D { int vertexCount = getVertexCount(); int floatCount = vertexCount * 4; // 每个顶点:x, y, u, v - if (buffer == null || buffer.capacity() < floatCount) { + if (buffer == null || buffer.remaining() < floatCount) { throw new IllegalArgumentException("Buffer is null or too small"); } buffer.clear(); for (int i = 0; i < vertexCount; i++) { - System.out.println("x:" + vertices[i * 2] + "y:" + vertices[i * 2 + 1]); - buffer.put(vertices[i * 2]); // x - buffer.put(vertices[i * 2 + 1]); // y - buffer.put(uvs[i * 2]); // u - buffer.put(uvs[i * 2 + 1]); // v + // 明确使用定位方法,避免下标算错 + //System.out.println("x:"+ getX(i) + "y:"+ getY(i)); + buffer.put(getX(i)); // x + buffer.put(getY(i)); // y + buffer.put(uvs[i * 2]); // u + buffer.put(uvs[i * 2 + 1]); // v } buffer.flip(); return buffer; @@ -446,7 +460,7 @@ public class Mesh2D { /** * 绘制网格(会在第一次绘制时自动上传到 GPU) */ - public void draw() { + public void draw(int shaderProgram, org.joml.Matrix3f modelMatrix) { if (!visible) return; if (indices == null || indices.length == 0) return; @@ -458,10 +472,46 @@ public class Mesh2D { texture.bind(); } + // 绑定 VAO + GL30.glBindVertexArray(vaoId); + + // 将 modelMatrix 上传到 shader 的 uniform "uModel"(如果 shader 有此 uniform) + int loc = GL20.glGetUniformLocation(shaderProgram, "uModel"); + if (loc != -1) { + // 用一个 FloatBuffer 传递 3x3 矩阵 + java.nio.FloatBuffer fb = org.lwjgl.system.MemoryUtil.memAllocFloat(9); + try { + modelMatrix.get(fb); // JOML 将矩阵写入 buffer(列主序,适合 OpenGL) + fb.flip(); + GL20.glUniformMatrix3fv(loc, false, fb); + } finally { + org.lwjgl.system.MemoryUtil.memFree(fb); + } + } + + // 绘制 + GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL11.GL_UNSIGNED_INT, 0); + + // 解绑 + GL30.glBindVertexArray(0); + + if (texture != null) { + texture.unbind(); + } + } + + public void draw() { + if (!visible) return; + if (indices == null || indices.length == 0) return; + if (!uploaded) { + uploadToGPU(); + } + if (texture != null) { + texture.bind(); + } GL30.glBindVertexArray(vaoId); GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL11.GL_UNSIGNED_INT, 0); GL30.glBindVertexArray(0); - if (texture != null) { texture.unbind(); } @@ -582,6 +632,15 @@ public class Mesh2D { } } + /** 标记或查询网格顶点是否已经被烘焙到世界坐标 */ + public void setBakedToWorld(boolean baked) { + this.bakedToWorld = baked; + } + + public boolean isBakedToWorld() { + return bakedToWorld; + } + // ==================== Object 方法 ==================== @Override @@ -617,4 +676,4 @@ public class Mesh2D { ", bounds=" + getBounds() + '}'; } -} \ No newline at end of file +} diff --git a/src/main/java/com/chuangzhou/vivid2D/test/ModelRenderTest.java b/src/main/java/com/chuangzhou/vivid2D/test/ModelRenderTest.java index 773161e..e487a3c 100644 --- a/src/main/java/com/chuangzhou/vivid2D/test/ModelRenderTest.java +++ b/src/main/java/com/chuangzhou/vivid2D/test/ModelRenderTest.java @@ -108,7 +108,7 @@ public class ModelRenderTest { // body 放在屏幕中心 ModelPart body = testModel.createPart("body"); - body.setPosition(400, 320); + body.setPosition(0, 0); // 身体网格:宽 80 高 120 Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 80, 120); bodyMesh.setTexture(createSolidTexture(64, 128, 0xFF4A6AFF)); // 蓝衣 @@ -151,6 +151,7 @@ public class ModelRenderTest { // 建立层级:body 为根,其他作为 body 的子节点 //testModel.addPart(body); + body.addChild(head); body.addChild(leftArm); body.addChild(rightArm);