Files
window-axis-innovators-box/src/main/java/com/chuangzhou/vivid2D/test/ModelTest2.java
tzdwindows 7 27744d4b5c refactor(render):重构渲染系统架构
- 将 BufferBuilder 移至 systems.buffer 包并增强功能- 添加 BuiltBuffer 和 RenderState 内部类支持状态管理- 新增 BufferUploader 类处理缓冲区上传和状态应用
- 引入 RenderSystem 统一封装 OpenGL 调用
- Mesh2D 和 ModelRender 更新使用新的渲染系统接口- ModelGLPanel 适配新包结构并使用 RenderSystem 初始化
- 移除旧版 LightSource 构造函数- 整体提升渲染代码的模块化和可维护性

重要更新
- 重写渲染器
- 移除辉光,采用旧版着色器渲染,任何有关辉光的将在下一个版本彻底删除
2025-10-17 01:48:07 +08:00

684 lines
24 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
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;
import java.util.ArrayList;
import java.util.List;
/**
* 物理系统使用实例 - 演示弹簧、重力和碰撞效果
* @author tzdwindows 7
*/
public class ModelTest2 {
private static final int WINDOW_WIDTH = 1000;
private static final int WINDOW_HEIGHT = 700;
private static final String WINDOW_TITLE = "Physics System Demo";
private long window;
private boolean running = true;
private Model2D physicsModel;
private PhysicsSystem physics;
// 测试用例控制
private int testCase = 5;
private boolean gravityEnabled = true;
private boolean springsEnabled = true;
// 存储部件引用,用于清理
private List<ModelPart> currentParts = new ArrayList<>();
// 所有测试基点(初始 xy = 0,0
private final Vector2f initialOrigin = new Vector2f(0, 0);
public static void main(String[] args) {
new ModelTest2().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) % 6; // 支持 0..5 共 6 个用例
setupTestCase();
}
if (key == GLFW.GLFW_KEY_G && action == GLFW.GLFW_RELEASE) {
gravityEnabled = !gravityEnabled;
physics.setGravity(gravityEnabled ? new Vector2f(0, -98.0f) : new Vector2f(0, 0));
System.out.println("Gravity " + (gravityEnabled ? "ENABLED" : "DISABLED"));
}
if (key == GLFW.GLFW_KEY_S && action == GLFW.GLFW_RELEASE) {
springsEnabled = !springsEnabled;
toggleSprings(springsEnabled);
System.out.println("Springs " + (springsEnabled ? "ENABLED" : "DISABLED"));
}
if (key == GLFW.GLFW_KEY_R && action == GLFW.GLFW_RELEASE) {
resetPhysics();
}
if (key == GLFW.GLFW_KEY_C && action == GLFW.GLFW_RELEASE) {
applyRandomForce();
}
});
GLFW.glfwSetWindowSizeCallback(window, (wnd, w, h) -> ModelRender.setViewport(w, h));
GLFW.glfwMakeContextCurrent(window);
GLFW.glfwSwapInterval(1);
GLFW.glfwShowWindow(window);
GL.createCapabilities();
createPhysicsModel();
ModelRender.initialize();
System.out.println("Physics System Demo Initialized");
printControls();
}
private void printControls() {
System.out.println("\n=== Controls ===");
System.out.println("ESC - Exit");
System.out.println("SPACE - Change test case");
System.out.println("G - Toggle gravity");
System.out.println("S - Toggle springs");
System.out.println("R - Reset physics");
System.out.println("C - Apply random force");
System.out.println("================\n");
}
/**
* 创建物理测试模型
*/
private void createPhysicsModel() {
physicsModel = new Model2D("PhysicsDemo");
physics = physicsModel.getPhysics();
// 配置物理系统
physics.setGravity(new Vector2f(0, -98.0f));
physics.setAirResistance(0.05f);
physics.setTimeScale(1.0f);
physics.setEnabled(true);
physics.initialize();
setupTestCase();
}
/**
* 设置不同的测试用例
*/
private void setupTestCase() {
// 清理之前的设置
clearCurrentParts();
physics.reset();
switch (testCase) {
case 0:
setupSpringChain();
break;
case 1:
setupClothSimulation();
break;
case 2:
setupPendulum();
break;
case 3:
setupSoftBody();
break;
case 4:
setupWindTest();
break;
case 5:
setupFreeFallTest();
break;
}
System.out.println("Test Case " + testCase + ": " + getTestCaseName(testCase));
}
/**
* 清理当前部件
*/
private void clearCurrentParts() {
// 由于无法直接清除模型的parts列表我们创建一个新模型
physicsModel = new Model2D("PhysicsDemo");
currentParts.clear();
// 重新配置物理系统
physics = physicsModel.getPhysics();
physics.setGravity(new Vector2f(0, -98.0f));
physics.setAirResistance(0.05f);
physics.setTimeScale(1.0f);
physics.setEnabled(true);
physics.initialize();
}
/**
* 测试用例1: 弹簧链
*/
private void setupSpringChain() {
// 创建5个连接的粒子基于 initialOrigin因此首个粒子是 (0,0)
for (int i = 0; i < 5; i++) {
Vector2f position = new Vector2f(initialOrigin.x + i * 60, initialOrigin.y + i * 20);
PhysicsSystem.PhysicsParticle particle = physics.addParticle("particle_" + i, position, 1.0f);
// 第一个粒子固定(位于 initialOrigin
if (i == 0) {
particle.setMovable(false);
}
// 创建对应的模型部件
ModelPart part = physicsModel.createPart("part_" + i);
part.setPosition(position.x, position.y);
currentParts.add(part);
// 创建圆形网格
Mesh2D circleMesh = createCircleMesh("circle_" + i, 20, getColorForIndex(i));
part.addMesh(circleMesh);
physicsModel.addMesh(circleMesh);
// 将部件设置为粒子的用户数据,用于同步位置
particle.setUserData(part);
// 添加弹簧连接(除了第一个粒子)
if (i > 0) {
PhysicsSystem.PhysicsParticle prevParticle = physics.getParticle("particle_" + (i - 1));
physics.addSpring("spring_" + (i - 1), prevParticle, particle, 60.0f, 0.3f, 0.1f);
}
}
}
/**
* 测试用例2: 布料模拟
*/
private void setupClothSimulation() {
int rows = 4;
int cols = 6;
float spacing = 40.0f;
// 创建布料网格,基于 initialOrigin
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
int index = y * cols + x;
Vector2f position = new Vector2f(initialOrigin.x + x * spacing, initialOrigin.y + y * spacing);
PhysicsSystem.PhysicsParticle particle = physics.addParticle("cloth_" + index, position, 0.8f);
// 固定顶部行的粒子y==0
if (y == 0) {
particle.setMovable(false);
}
ModelPart part = physicsModel.createPart("cloth_part_" + index);
part.setPosition(position.x, position.y);
currentParts.add(part);
Mesh2D squareMesh = createSquareMesh("square_" + index, 15, getColorForIndex(index));
part.addMesh(squareMesh);
physicsModel.addMesh(squareMesh);
// 将部件设置为粒子的用户数据
particle.setUserData(part);
// 添加水平弹簧连接
if (x > 0) {
PhysicsSystem.PhysicsParticle leftParticle = physics.getParticle("cloth_" + (index - 1));
physics.addSpring("h_spring_" + index, leftParticle, particle, spacing, 0.4f, 0.05f);
}
// 添加垂直弹簧连接
if (y > 0) {
PhysicsSystem.PhysicsParticle topParticle = physics.getParticle("cloth_" + (index - cols));
physics.addSpring("v_spring_" + index, topParticle, particle, spacing, 0.4f, 0.05f);
}
}
}
}
/**
* 测试用例3: 钟摆系统
*/
private void setupPendulum() {
// 创建钟摆锚点(位于 initialOrigin
Vector2f anchorPos = new Vector2f(initialOrigin);
PhysicsSystem.PhysicsParticle anchor = physics.addParticle("anchor", anchorPos, 0.0f);
anchor.setMovable(false); // 固定锚点
// 创建钟摆摆锤(相对锚点水平分布)
for (int i = 0; i < 3; i++) {
Vector2f pendulumPos = new Vector2f(initialOrigin.x + (i - 1) * 120, initialOrigin.y + 200);
PhysicsSystem.PhysicsParticle particle = physics.addParticle("pendulum_" + i, pendulumPos, 2.0f);
// 检查粒子是否成功创建
if (particle == null) {
System.err.println("Failed to create pendulum particle: pendulum_" + i);
continue;
}
ModelPart part = physicsModel.createPart("pendulum_part_" + i);
part.setPosition(pendulumPos.x, pendulumPos.y);
currentParts.add(part);
Mesh2D ballMesh = createCircleMesh("ball_" + i, 25, getColorForIndex(i));
part.addMesh(ballMesh);
physicsModel.addMesh(ballMesh);
// 将部件设置为粒子的用户数据
particle.setUserData(part);
// 连接到锚点 - 确保anchor和particle都不为null
if (anchor != null && particle != null) {
float length = 200 + i * 50;
physics.addSpring("pendulum_spring_" + i, anchor, particle, length, 0.1f, 0.02f);
}
}
}
/**
* 测试用例4: 软体模拟
*/
private void setupSoftBody() {
// 创建软体圆形,中心在 initialOrigin
int points = 8;
float radius = 60.0f;
Vector2f center = new Vector2f(initialOrigin);
// 第一步:先创建所有粒子
List<PhysicsSystem.PhysicsParticle> particlesList = new ArrayList<>();
for (int i = 0; i < points; i++) {
float angle = (float) (i * 2 * Math.PI / points);
Vector2f position = new Vector2f(
center.x + radius * (float) Math.cos(angle),
center.y + radius * (float) Math.sin(angle)
);
PhysicsSystem.PhysicsParticle particle = physics.addParticle("soft_" + i, position, 0.5f);
particlesList.add(particle);
ModelPart part = physicsModel.createPart("soft_part_" + i);
part.setPosition(position.x, position.y);
currentParts.add(part);
Mesh2D pointMesh = createCircleMesh("point_" + i, 12, 0xFF00FFFF);
part.addMesh(pointMesh);
physicsModel.addMesh(pointMesh);
// 将部件设置为粒子的用户数据
particle.setUserData(part);
}
// 第二步:再创建所有弹簧连接
for (int i = 0; i < points; i++) {
PhysicsSystem.PhysicsParticle particle = particlesList.get(i);
// 连接到相邻点
int next = (i + 1) % points;
PhysicsSystem.PhysicsParticle nextParticle = particlesList.get(next);
physics.addSpring("soft_spring_" + i, particle, nextParticle,
radius * 2 * (float) Math.sin(Math.PI / points), 0.5f, 0.1f);
// 连接到对面的点(增加稳定性)
if (i < points / 2) {
int opposite = (i + points / 2) % points;
PhysicsSystem.PhysicsParticle oppositeParticle = particlesList.get(opposite);
physics.addSpring("cross_spring_" + i, particle, oppositeParticle,
radius * 2, 0.2f, 0.05f);
}
}
}
/**
* 测试用例5: 自由落体测试
*/
private void setupFreeFallTest() {
// 创建地面(位于 initialOrigin
Vector2f groundPos = new Vector2f(initialOrigin);
PhysicsSystem.PhysicsParticle ground = physics.addParticle("ground", groundPos, 0.0f);
ground.setMovable(false);
// 创建多个不同质量的物体从不同高度掉落(相对于 initialOrigin
for (int i = 0; i < 5; i++) {
Vector2f position = new Vector2f(initialOrigin.x + 300 + i * 100, initialOrigin.y + 600 - i * 50);
float mass = 1.0f + i * 0.5f; // 不同质量
PhysicsSystem.PhysicsParticle particle = physics.addParticle("fall_" + i, position, mass);
ModelPart part = physicsModel.createPart("fall_part_" + i);
part.setPosition(position.x, position.y);
currentParts.add(part);
Mesh2D ballMesh = createCircleMesh("fall_ball_" + i, 15 + i * 3, getColorForIndex(i));
part.addMesh(ballMesh);
physicsModel.addMesh(ballMesh);
particle.setUserData(part);
}
// 添加地面碰撞体(基于 initialOrigin
physics.addRectangleCollider("ground_collider", groundPos, 800, 20);
}
/**
* 测试用例6: 风力测试
*/
private void setupWindTest() {
// 创建布料用于测试风力,基于 initialOrigin
int rows = 6;
int cols = 8;
float spacing = 35.0f;
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
int index = y * cols + x;
// 布料放在 initialOrigin.x + ..., initialOrigin.y - y*spacing + 500 以方便显示
Vector2f position = new Vector2f(initialOrigin.x + x * spacing, initialOrigin.y - y * spacing + 500);
PhysicsSystem.PhysicsParticle particle = physics.addParticle("wind_cloth_" + index, position, 0.6f);
// 固定顶部行的粒子y==0
if (y == 0) {
particle.setMovable(false);
}
ModelPart part = physicsModel.createPart("wind_part_" + index);
part.setPosition(position.x, position.y);
currentParts.add(part);
Mesh2D squareMesh = createSquareMesh("wind_square_" + index, 12, getColorForIndex(index));
part.addMesh(squareMesh);
physicsModel.addMesh(squareMesh);
particle.setUserData(part);
// 添加水平弹簧连接
if (x > 0) {
PhysicsSystem.PhysicsParticle leftParticle = physics.getParticle("wind_cloth_" + (index - 1));
physics.addSpring("wind_h_spring_" + index, leftParticle, particle, spacing, 0.3f, 0.05f);
}
// 添加垂直弹簧连接
if (y > 0) {
PhysicsSystem.PhysicsParticle topParticle = physics.getParticle("wind_cloth_" + (index - cols));
physics.addSpring("wind_v_spring_" + index, topParticle, particle, spacing, 0.3f, 0.05f);
}
}
}
}
/**
* 应用风力效果
*/
private void applyWindEffect() {
// 随机风力方向
float windStrength = 50.0f;
float windDirection = (float) (Math.random() * 2 * Math.PI); // 随机方向
Vector2f windForce = new Vector2f(
(float) Math.cos(windDirection) * windStrength,
(float) Math.sin(windDirection) * windStrength
);
// 对所有可移动粒子应用风力
for (PhysicsSystem.PhysicsParticle particle : physics.getParticles().values()) {
if (particle.isMovable()) {
// 风力随粒子高度变化(模拟真实风)
float heightFactor = particle.getPosition().y / 500.0f;
Vector2f adjustedWind = new Vector2f(windForce).mul(heightFactor);
particle.addForce(adjustedWind);
}
}
System.out.println("Wind applied: " + windForce);
}
/**
* 应用持续风力(周期性)
*/
private void applyContinuousWind(float deltaTime) {
// 模拟周期性风力
float time = System.nanoTime() * 0.000000001f;
float windStrength = 30.0f + (float) Math.sin(time * 2) * 20.0f; // 周期性变化
Vector2f windForce = new Vector2f(windStrength, 0); // 主要水平方向
for (PhysicsSystem.PhysicsParticle particle : physics.getParticles().values()) {
if (particle.isMovable()) {
particle.addForce(new Vector2f(windForce));
}
}
}
/**
* 创建圆形网格 - 修正版本
*/
private Mesh2D createCircleMesh(String name, float radius, int color) {
int segments = 16;
int vertexCount = segments + 1; // 中心点 + 圆周点
float[] vertices = new float[vertexCount * 2];
float[] uvs = new float[vertexCount * 2];
int[] indices = new int[segments * 3];
// 中心点 (索引0)
vertices[0] = 0;
vertices[1] = 0;
uvs[0] = 0.5f;
uvs[1] = 0.5f;
// 圆周点 (索引1到segments)
for (int i = 0; i < segments; i++) {
float angle = (float) (i * 2 * Math.PI / segments);
int vertexIndex = (i + 1) * 2;
vertices[vertexIndex] = radius * (float) Math.cos(angle);
vertices[vertexIndex + 1] = radius * (float) Math.sin(angle);
uvs[vertexIndex] = (float) Math.cos(angle) * 0.5f + 0.5f;
uvs[vertexIndex + 1] = (float) Math.sin(angle) * 0.5f + 0.5f;
}
// 三角形索引 - 每个三角形连接中心点和两个相邻的圆周点
for (int i = 0; i < segments; i++) {
int triangleIndex = i * 3;
indices[triangleIndex] = 0; // 中心点
indices[triangleIndex + 1] = i + 1; // 当前圆周点
indices[triangleIndex + 2] = (i + 1) % segments + 1; // 下一个圆周点
}
Mesh2D mesh = new Mesh2D(name, vertices, uvs, indices);
mesh.setTexture(createSolidColorTexture(name + "_tex", color));
return mesh;
}
/**
* 创建方形网格
*/
private Mesh2D createSquareMesh(String name, float size, int color) {
float halfSize = size / 2;
float[] vertices = {
-halfSize, -halfSize,
halfSize, -halfSize,
halfSize, halfSize,
-halfSize, halfSize
};
float[] uvs = {
0, 0,
1, 0,
1, 1,
0, 1
};
int[] indices = {0, 1, 2, 0, 2, 3};
Mesh2D mesh = new Mesh2D(name, vertices, uvs, indices);
mesh.setTexture(createSolidColorTexture(name + "_tex", color));
return mesh;
}
/**
* 创建纯色纹理
*/
private Texture createSolidColorTexture(String name, int color) {
int width = 64, height = 64;
ByteBuffer buf = MemoryUtil.memAlloc(width * height * 4);
byte r = (byte) ((color >> 16) & 0xFF);
byte g = (byte) ((color >> 8) & 0xFF);
byte b = (byte) (color & 0xFF);
for (int i = 0; i < width * height; i++) {
buf.put(r).put(g).put(b).put((byte) 255);
}
buf.flip();
Texture texture = new Texture(name, width, height, Texture.TextureFormat.RGBA, buf);
MemoryUtil.memFree(buf);
return texture;
}
/**
* 根据索引获取不同颜色
*/
private int getColorForIndex(int index) {
int[] colors = {
0xFF00FF00, // 绿色
0xFFFF0000, // 红色
0xFF0000FF, // 蓝色
0xFFFFFF00, // 黄色
0xFFFF00FF, // 紫色
0xFF00FFFF // 青色
};
return colors[index % colors.length];
}
/**
* 获取测试用例名称
*/
private String getTestCaseName(int testCase) {
switch (testCase) {
case 0: return "Spring Chain";
case 1: return "Cloth Simulation";
case 2: return "Pendulum System";
case 3: return "Soft Body";
case 4: return "Wind Test";
case 5: return "Free Fall Test";
default: return "Unknown";
}
}
/**
* 切换弹簧状态
*/
private void toggleSprings(boolean enabled) {
for (PhysicsSystem.PhysicsSpring spring : physics.getSprings()) {
spring.setEnabled(enabled);
}
}
/**
* 重置物理系统
*/
private void resetPhysics() {
setupTestCase();
System.out.println("Physics reset");
}
/**
* 施加随机力
*/
private void applyRandomForce() {
for (PhysicsSystem.PhysicsParticle particle : physics.getParticles().values()) {
if (particle.isMovable()) {
float forceX = (float) (Math.random() - 0.5) * 200f;
float forceY = (float) (Math.random() - 0.5) * 200f;
particle.addForce(new Vector2f(forceX, forceY));
}
}
System.out.println("Random forces applied");
}
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(last);
GLFW.glfwSwapBuffers(window);
GLFW.glfwPollEvents();
}
}
private void update(float dt) {
// 更新物理系统 - 会自动同步到模型部件
physicsModel.update(dt);
}
private void render(long last) {
RenderSystem.setClearColor(0.1f, 0.1f, 0.15f, 1.0f);
ModelRender.render(last, physicsModel);
}
private void cleanup() {
System.out.println("Cleaning up physics demo resources...");
ModelRender.cleanup();
Texture.cleanupAll();
if (window != MemoryUtil.NULL) GLFW.glfwDestroyWindow(window);
GLFW.glfwTerminate();
GLFW.glfwSetErrorCallback(null).free();
System.out.println("Physics demo finished");
}
}