feat(render): 实现模型旋转中心点支持- 为 ModelPart 添加 pivot 属性,支持设置旋转中心点
- 更新局部变换矩阵计算,考虑 pivot 对旋转和平移的影响 - 在 Mesh2D 中增强着色器 uniform 设置,兼容 uModelMatrix 和 uModel- 添加 setPivot 和 getPivot 方法,支持动态调整旋转中心- 创建测试用例 ModelRenderTest2,验证不同 pivot 点的旋转效果 -修复纹理绑定逻辑,确保渲染时正确应用纹理 - 添加调试纹理生成功能,便于视觉验证 pivot 效果
This commit is contained in:
230
src/main/java/com/chuangzhou/vivid2D/test/ModelRenderTest2.java
Normal file
230
src/main/java/com/chuangzhou/vivid2D/test/ModelRenderTest2.java
Normal file
@@ -0,0 +1,230 @@
|
||||
package com.chuangzhou.vivid2D.test;
|
||||
|
||||
import com.chuangzhou.vivid2D.render.ModelRender;
|
||||
import com.chuangzhou.vivid2D.render.model.Model2D;
|
||||
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Texture;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Vector2f;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* 用于测试中心点旋转
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class ModelRenderTest2 {
|
||||
|
||||
private static final int WINDOW_WIDTH = 800;
|
||||
private static final int WINDOW_HEIGHT = 600;
|
||||
private static final String WINDOW_TITLE = "Simple Pivot Test";
|
||||
|
||||
private long window;
|
||||
private boolean running = true;
|
||||
private Model2D testModel;
|
||||
private float rotationAngle = 0f;
|
||||
private int testCase = 0;
|
||||
private Mesh2D squareMesh;
|
||||
public static void main(String[] args) {
|
||||
new ModelRenderTest2().run();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
init();
|
||||
loop();
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private void init() {
|
||||
GLFWErrorCallback.createPrint(System.err).set();
|
||||
|
||||
if (!GLFW.glfwInit()) {
|
||||
throw new IllegalStateException("Unable to initialize GLFW");
|
||||
}
|
||||
|
||||
GLFW.glfwDefaultWindowHints();
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE);
|
||||
|
||||
window = GLFW.glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, MemoryUtil.NULL, MemoryUtil.NULL);
|
||||
if (window == MemoryUtil.NULL) throw new RuntimeException("Failed to create GLFW window");
|
||||
|
||||
GLFWVidMode vidMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
|
||||
GLFW.glfwSetWindowPos(window,
|
||||
(vidMode.width() - WINDOW_WIDTH) / 2,
|
||||
(vidMode.height() - WINDOW_HEIGHT) / 2);
|
||||
|
||||
GLFW.glfwSetKeyCallback(window, (wnd, key, scancode, action, mods) -> {
|
||||
if (key == GLFW.GLFW_KEY_ESCAPE && action == GLFW.GLFW_RELEASE) running = false;
|
||||
if (key == GLFW.GLFW_KEY_SPACE && action == GLFW.GLFW_RELEASE) {
|
||||
testCase = (testCase + 1) % 3;
|
||||
updatePivotPoint();
|
||||
}
|
||||
if (key == GLFW.GLFW_KEY_R && action == GLFW.GLFW_RELEASE) {
|
||||
resetPosition();
|
||||
}
|
||||
});
|
||||
|
||||
GLFW.glfwSetWindowSizeCallback(window, (wnd, w, h) -> ModelRender.setViewport(w, h));
|
||||
|
||||
GLFW.glfwMakeContextCurrent(window);
|
||||
GLFW.glfwSwapInterval(1);
|
||||
GLFW.glfwShowWindow(window);
|
||||
|
||||
GL.createCapabilities();
|
||||
|
||||
createSimpleTestModel();
|
||||
ModelRender.initialize();
|
||||
|
||||
System.out.println("Simple pivot test initialized");
|
||||
System.out.println("Controls: ESC = exit | SPACE = change pivot | R = reset position");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single square mesh with vertices centered at origin
|
||||
*/
|
||||
private void createSimpleTestModel() {
|
||||
testModel = new Model2D("SimpleTest");
|
||||
|
||||
ModelPart square = testModel.createPart("square");
|
||||
square.setPosition(0, 0); // center of window
|
||||
square.setPivot(0,0);
|
||||
// Create 80x80 quad centered at origin
|
||||
squareMesh = Mesh2D.createQuad("square_mesh", 80, 80);
|
||||
// Shift vertices so center is at (0,0)
|
||||
//for (int i = 0; i < squareMesh.getVertexCount(); i++) {
|
||||
// Vector2f v = squareMesh.getVertex(i);
|
||||
// v.sub(0, 0);
|
||||
// squareMesh.setVertex(i, v.x, v.y);
|
||||
//}
|
||||
|
||||
squareMesh.setTexture(createDiagnosticTexture());
|
||||
square.addMesh(squareMesh); // do NOT bake to world coordinates
|
||||
|
||||
System.out.println("Simple test model created with one part only");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a diagnostic texture to see pivot visually
|
||||
*/
|
||||
private Texture createDiagnosticTexture() {
|
||||
int width = 80, height = 80;
|
||||
ByteBuffer buf = MemoryUtil.memAlloc(width * height * 4);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// center cross
|
||||
if (x == width / 2 || y == height / 2) {
|
||||
buf.put((byte) 255).put((byte) 255).put((byte) 255).put((byte) 255);
|
||||
} else if (x < width / 2 && y < height / 2) {
|
||||
buf.put((byte) 255).put((byte) 0).put((byte) 0).put((byte) 255); // top-left red
|
||||
} else if (x >= width / 2 && y < height / 2) {
|
||||
buf.put((byte) 0).put((byte) 255).put((byte) 0).put((byte) 255); // top-right green
|
||||
} else if (x < width / 2 && y >= height / 2) {
|
||||
buf.put((byte) 0).put((byte) 0).put((byte) 255).put((byte) 255); // bottom-left blue
|
||||
} else {
|
||||
buf.put((byte) 255).put((byte) 255).put((byte) 0).put((byte) 255); // bottom-right yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.flip();
|
||||
Texture texture = new Texture("diagnostic", width, height, Texture.TextureFormat.RGBA, buf);
|
||||
MemoryUtil.memFree(buf);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private void updatePivotPoint() {
|
||||
ModelPart square = testModel.getPart("square");
|
||||
if (square != null) {
|
||||
switch (testCase) {
|
||||
case 0:
|
||||
square.setPivot(0, 0);
|
||||
System.out.println("Pivot: center (0,0) - should rotate around center");
|
||||
break;
|
||||
case 1:
|
||||
square.setPivot(-40, 40); // top-left corner
|
||||
System.out.println("Pivot: top-left (-40,40) - should rotate around top-left");
|
||||
break;
|
||||
case 2:
|
||||
square.setPivot(40, -40); // bottom-right corner
|
||||
System.out.println("Pivot: bottom-right (40,-40) - should rotate around bottom-right");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void resetPosition() {
|
||||
ModelPart square = testModel.getPart("square");
|
||||
if (square != null) {
|
||||
square.setPosition(400, 300);
|
||||
square.setRotation(0);
|
||||
rotationAngle = 0;
|
||||
System.out.println("Position reset");
|
||||
}
|
||||
}
|
||||
|
||||
private void loop() {
|
||||
long last = System.nanoTime();
|
||||
double nsPerUpdate = 1_000_000_000.0 / 60.0;
|
||||
double accumulator = 0.0;
|
||||
|
||||
while (running && !GLFW.glfwWindowShouldClose(window)) {
|
||||
long now = System.nanoTime();
|
||||
accumulator += (now - last) / nsPerUpdate;
|
||||
last = now;
|
||||
|
||||
while (accumulator >= 1.0) {
|
||||
update(1.0f / 60.0f);
|
||||
accumulator -= 1.0;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
GLFW.glfwSwapBuffers(window);
|
||||
GLFW.glfwPollEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void update(float dt) {
|
||||
rotationAngle += dt * 1.5f;
|
||||
|
||||
ModelPart square = testModel.getPart("square");
|
||||
if (square != null) {
|
||||
square.setRotation(rotationAngle);
|
||||
}
|
||||
|
||||
testModel.update(dt);
|
||||
}
|
||||
|
||||
private void render() {
|
||||
ModelRender.setClearColor(0.2f, 0.2f, 0.3f, 1.0f);
|
||||
ModelRender.render(1.0f / 60.0f, testModel);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
System.out.println("Cleaning up resources...");
|
||||
ModelRender.cleanup();
|
||||
Texture.cleanupAll();
|
||||
if (window != MemoryUtil.NULL) GLFW.glfwDestroyWindow(window);
|
||||
GLFW.glfwTerminate();
|
||||
GLFW.glfwSetErrorCallback(null).free();
|
||||
System.out.println("Test finished");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user