feat(render): 添加光源与物理系统支持

- 新增 BufferBuilder 工具类用于简化顶点数据提交
- 实现 LightSource 和 LightSourceData 类以支持光源管理- 在 Model2D 中集成光源系统,支持序列化与反序列化
- 扩展 ModelData 以支持物理系统数据的完整序列化
- 重构 ModelRender以支持物理系统应用及碰撞箱渲染
- 添加粒子、弹簧、约束与碰撞体的数据结构与序列化逻辑
- 实现变形器的序列化接口以支持参数驱动动画的持久化
This commit is contained in:
tzdwindows 7
2025-10-11 20:21:11 +08:00
parent 22af92cd84
commit 9cde0192fd
18 changed files with 3071 additions and 276 deletions

View File

@@ -0,0 +1,682 @@
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 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) {
ModelRender.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");
}
}