feat(render): 添加光源与物理系统支持
- 新增 BufferBuilder 工具类用于简化顶点数据提交 - 实现 LightSource 和 LightSourceData 类以支持光源管理- 在 Model2D 中集成光源系统,支持序列化与反序列化 - 扩展 ModelData 以支持物理系统数据的完整序列化 - 重构 ModelRender以支持物理系统应用及碰撞箱渲染 - 添加粒子、弹簧、约束与碰撞体的数据结构与序列化逻辑 - 实现变形器的序列化接口以支持参数驱动动画的持久化
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
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.LightSource;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Texture;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
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.Random;
|
||||
|
||||
/**
|
||||
* ModelRenderLightingTest
|
||||
* 测试使用 Model2D + 光源进行简单光照渲染
|
||||
*/
|
||||
public class ModelRenderLightingTest {
|
||||
|
||||
private static final int WINDOW_WIDTH = 800;
|
||||
private static final int WINDOW_HEIGHT = 600;
|
||||
private static final String WINDOW_TITLE = "Vivid2D ModelRender Lighting Test";
|
||||
|
||||
private long window;
|
||||
private boolean running = true;
|
||||
|
||||
private Model2D model;
|
||||
private Random random = new Random();
|
||||
|
||||
private float animationTime = 0f;
|
||||
|
||||
public static void main(String[] args) {
|
||||
new ModelRenderLightingTest().run();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
init();
|
||||
loop();
|
||||
} 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;
|
||||
});
|
||||
|
||||
GLFW.glfwSetWindowSizeCallback(window, (wnd, w, h) -> ModelRender.setViewport(w, h));
|
||||
|
||||
GLFW.glfwMakeContextCurrent(window);
|
||||
GLFW.glfwSwapInterval(1);
|
||||
GLFW.glfwShowWindow(window);
|
||||
|
||||
GL.createCapabilities();
|
||||
|
||||
ModelRender.initialize();
|
||||
createModelWithLighting();
|
||||
|
||||
System.out.println("Lighting Test initialized");
|
||||
}
|
||||
|
||||
private void createModelWithLighting() {
|
||||
model = new Model2D("HumanoidLighting");
|
||||
|
||||
// 创建根部件 body
|
||||
ModelPart body = model.createPart("body");
|
||||
body.setPosition(0, 0);
|
||||
Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 80, 120);
|
||||
bodyMesh.setTexture(createSolidTexture(64, 128, 0xFF4A6AFF));
|
||||
body.addMesh(bodyMesh);
|
||||
|
||||
// head
|
||||
ModelPart head = model.createPart("head");
|
||||
head.setPosition(0, -90);
|
||||
Mesh2D headMesh = Mesh2D.createQuad("head_mesh", 60, 60);
|
||||
headMesh.setTexture(createHeadTexture());
|
||||
head.addMesh(headMesh);
|
||||
|
||||
// arms
|
||||
ModelPart leftArm = model.createPart("left_arm");
|
||||
leftArm.setPosition(-60, -20);
|
||||
Mesh2D leftArmMesh = Mesh2D.createQuad("left_arm_mesh", 18, 90);
|
||||
leftArmMesh.setTexture(createSolidTexture(16, 90, 0xFF6495ED));
|
||||
leftArm.addMesh(leftArmMesh);
|
||||
|
||||
ModelPart rightArm = model.createPart("right_arm");
|
||||
rightArm.setPosition(60, -20);
|
||||
Mesh2D rightArmMesh = Mesh2D.createQuad("right_arm_mesh", 18, 90);
|
||||
rightArmMesh.setTexture(createSolidTexture(16, 90, 0xFF6495ED));
|
||||
rightArm.addMesh(rightArmMesh);
|
||||
|
||||
// legs
|
||||
ModelPart leftLeg = model.createPart("left_leg");
|
||||
leftLeg.setPosition(-20, 90);
|
||||
Mesh2D leftLegMesh = Mesh2D.createQuad("left_leg_mesh", 20, 100);
|
||||
leftLegMesh.setTexture(createSolidTexture(20, 100, 0xFF4169E1));
|
||||
leftLeg.addMesh(leftLegMesh);
|
||||
|
||||
ModelPart rightLeg = model.createPart("right_leg");
|
||||
rightLeg.setPosition(20, 90);
|
||||
Mesh2D rightLegMesh = Mesh2D.createQuad("right_leg_mesh", 20, 100);
|
||||
rightLegMesh.setTexture(createSolidTexture(20, 100, 0xFF4169E1));
|
||||
rightLeg.addMesh(rightLegMesh);
|
||||
|
||||
// 层级关系
|
||||
body.addChild(head);
|
||||
body.addChild(leftArm);
|
||||
body.addChild(rightArm);
|
||||
body.addChild(leftLeg);
|
||||
body.addChild(rightLeg);
|
||||
|
||||
LightSource ambientLight = new LightSource(
|
||||
new Vector3f(0.5f, 0.5f, 0.5f), // 灰色
|
||||
0.3f
|
||||
);
|
||||
ambientLight.setAmbient(true);
|
||||
model.addLight(ambientLight);
|
||||
|
||||
// 添加光源
|
||||
model.addLight(new LightSource(new Vector2f(-100, -100), new Vector3f(1f, 0f, 0f), 20f));
|
||||
model.addLight(new LightSource(new Vector2f(150, 150), new Vector3f(0f, 0f, 1f), 20f));
|
||||
}
|
||||
|
||||
private Texture createSolidTexture(int w, int h, int rgba) {
|
||||
ByteBuffer buf = MemoryUtil.memAlloc(w * h * 4);
|
||||
byte a = (byte)((rgba >> 24) & 0xFF);
|
||||
byte r = (byte)((rgba >> 16) & 0xFF);
|
||||
byte g = (byte)((rgba >> 8) & 0xFF);
|
||||
byte b = (byte)(rgba & 0xFF);
|
||||
for(int i=0;i<w*h;i++) buf.put(r).put(g).put(b).put(a);
|
||||
buf.flip();
|
||||
Texture t = new Texture("solid_" + rgba, w, h, Texture.TextureFormat.RGBA, buf);
|
||||
MemoryUtil.memFree(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
private Texture createHeadTexture() {
|
||||
int width = 64, height = 64;
|
||||
int[] pixels = new int[width * height];
|
||||
for(int y=0;y<height;y++) {
|
||||
for(int x=0;x<width;x++) {
|
||||
float dx = (x - width/2f)/(width/2f);
|
||||
float dy = (y - height/2f)/(height/2f);
|
||||
float dist = (float)Math.sqrt(dx*dx + dy*dy);
|
||||
int alpha = dist>1.0f?0:255;
|
||||
int r = (int)(240*(1f - dist*0.25f));
|
||||
int g = (int)(200*(1f - dist*0.25f));
|
||||
int b = (int)(180*(1f - dist*0.25f));
|
||||
pixels[y*width + x] = (alpha<<24)|(r<<16)|(g<<8)|b;
|
||||
}
|
||||
}
|
||||
return new Texture("head_tex", width, height, Texture.TextureFormat.RGBA, pixels);
|
||||
}
|
||||
|
||||
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) {
|
||||
animationTime += dt;
|
||||
float armSwing = (float)Math.sin(animationTime*3f)*0.7f;
|
||||
float legSwing = (float)Math.sin(animationTime*3f + Math.PI)*0.6f;
|
||||
float headRot = (float)Math.sin(animationTime*1.4f)*0.15f;
|
||||
|
||||
ModelPart leftArm = model.getPart("left_arm");
|
||||
ModelPart rightArm = model.getPart("right_arm");
|
||||
ModelPart leftLeg = model.getPart("left_leg");
|
||||
ModelPart rightLeg = model.getPart("right_leg");
|
||||
ModelPart head = model.getPart("head");
|
||||
|
||||
if(leftArm!=null) leftArm.setRotation(-0.8f*armSwing - 0.2f);
|
||||
if(rightArm!=null) rightArm.setRotation(0.8f*armSwing + 0.2f);
|
||||
if(leftLeg!=null) leftLeg.setRotation(0.6f*legSwing);
|
||||
if(rightLeg!=null) rightLeg.setRotation(-0.6f*legSwing);
|
||||
if(head!=null) head.setRotation(headRot);
|
||||
|
||||
model.update(dt);
|
||||
}
|
||||
|
||||
private void render() {
|
||||
ModelRender.setClearColor(0.18f,0.18f,0.25f,1.0f);
|
||||
ModelRender.render(1f/60f, model);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
ModelRender.cleanup();
|
||||
Texture.cleanupAll();
|
||||
if(window!= MemoryUtil.NULL) GLFW.glfwDestroyWindow(window);
|
||||
GLFW.glfwTerminate();
|
||||
GLFW.glfwSetErrorCallback(null).free();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ 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.PhysicsSystem;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Texture;
|
||||
import org.joml.Vector2f;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
@@ -105,6 +107,13 @@ public class ModelRenderTest {
|
||||
private void createTestModel() {
|
||||
testModel = new Model2D("Humanoid");
|
||||
|
||||
PhysicsSystem physics = testModel.getPhysics();
|
||||
physics.setGravity(new Vector2f(0, -98.0f));
|
||||
physics.setAirResistance(0.05f);
|
||||
physics.setTimeScale(1.0f);
|
||||
physics.setEnabled(true);
|
||||
physics.initialize();
|
||||
|
||||
// body 放在屏幕中心
|
||||
ModelPart body = testModel.createPart("body");
|
||||
body.setPosition(0, 0);
|
||||
|
||||
@@ -3,12 +3,8 @@ package com.chuangzhou.vivid2D.test;
|
||||
import com.chuangzhou.vivid2D.render.model.Model2D;
|
||||
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||
import com.chuangzhou.vivid2D.render.model.util.AnimationLayer;
|
||||
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
|
||||
import com.chuangzhou.vivid2D.render.model.util.ModelPose;
|
||||
import com.chuangzhou.vivid2D.render.model.util.BoundingBox;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Texture;
|
||||
import com.chuangzhou.vivid2D.render.model.transform.WaveDeformer;
|
||||
import com.chuangzhou.vivid2D.render.model.util.*;
|
||||
import org.joml.Vector2f;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
@@ -46,7 +42,8 @@ public class ModelTest {
|
||||
testPhysicsSystem();
|
||||
testComplexTransformations();
|
||||
testPerformance();
|
||||
|
||||
Model2D model = createTestModel();
|
||||
printModelState(model);
|
||||
} finally {
|
||||
// Cleanup OpenGL
|
||||
cleanupOpenGL();
|
||||
@@ -55,6 +52,231 @@ public class ModelTest {
|
||||
System.out.println("=== Model2D Extended Save and Load Test Complete ===");
|
||||
}
|
||||
|
||||
public static Model2D createTestModel() {
|
||||
Model2D model = new Model2D("full_test_model");
|
||||
model.setVersion("1.0.0");
|
||||
|
||||
// ==================== 创建部件层级 ====================
|
||||
ModelPart root = model.createPart("root");
|
||||
ModelPart body = model.createPart("body");
|
||||
ModelPart head = model.createPart("head");
|
||||
ModelPart leftArm = model.createPart("left_arm");
|
||||
ModelPart rightArm = model.createPart("right_arm");
|
||||
|
||||
root.addChild(body);
|
||||
body.addChild(head);
|
||||
body.addChild(leftArm);
|
||||
body.addChild(rightArm);
|
||||
|
||||
// ==================== 设置本地变换 ====================
|
||||
root.setPosition(0, 0);
|
||||
root.setRotation(0f);
|
||||
root.setScale(1f, 1f);
|
||||
|
||||
body.setPosition(0, -50);
|
||||
body.setRotation(10f); // body稍微旋转
|
||||
body.setScale(1.1f, 1.0f);
|
||||
|
||||
head.setPosition(0, -50);
|
||||
head.setRotation(-5f);
|
||||
head.setScale(1.0f, 1.0f);
|
||||
|
||||
leftArm.setPosition(-30, -20);
|
||||
leftArm.setRotation(20f);
|
||||
leftArm.setScale(1.0f, 0.9f);
|
||||
|
||||
rightArm.setPosition(30, -20);
|
||||
rightArm.setRotation(-20f);
|
||||
rightArm.setScale(1.0f, 0.9f);
|
||||
|
||||
// ==================== 添加网格 ====================
|
||||
Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 40, 80);
|
||||
Mesh2D headMesh = Mesh2D.createQuad("head_mesh", 50, 50);
|
||||
Mesh2D leftArmMesh = Mesh2D.createQuad("left_arm_mesh", 15, 50);
|
||||
Mesh2D rightArmMesh = Mesh2D.createQuad("right_arm_mesh", 15, 50);
|
||||
|
||||
model.addMesh(bodyMesh);
|
||||
model.addMesh(headMesh);
|
||||
model.addMesh(leftArmMesh);
|
||||
model.addMesh(rightArmMesh);
|
||||
|
||||
body.addMesh(bodyMesh);
|
||||
head.addMesh(headMesh);
|
||||
leftArm.addMesh(leftArmMesh);
|
||||
rightArm.addMesh(rightArmMesh);
|
||||
|
||||
// ==================== 添加纹理 ====================
|
||||
Texture bodyTex = Texture.createSolidColor("body_tex", 64, 64, 0xFFFF0000);
|
||||
Texture headTex = Texture.createSolidColor("head_tex", 64, 64, 0xFF00FF00);
|
||||
Texture armTex = Texture.createSolidColor("arm_tex", 32, 64, 0xFF0000FF);
|
||||
|
||||
bodyTex.ensurePixelDataCached();
|
||||
headTex.ensurePixelDataCached();
|
||||
armTex.ensurePixelDataCached();
|
||||
|
||||
model.addTexture(bodyTex);
|
||||
model.addTexture(headTex);
|
||||
model.addTexture(armTex);
|
||||
|
||||
bodyMesh.setTexture(bodyTex);
|
||||
headMesh.setTexture(headTex);
|
||||
leftArmMesh.setTexture(armTex);
|
||||
rightArmMesh.setTexture(armTex);
|
||||
|
||||
// ==================== 添加动画参数 ====================
|
||||
AnimationParameter smileParam = model.createParameter("smile", 0, 1, 0.5f);
|
||||
AnimationParameter walkParam = model.createParameter("walk", 0, 1, 0);
|
||||
AnimationParameter waveParam = model.createParameter("wave", 0, 1, 0);
|
||||
|
||||
// ==================== 添加 Deformer ====================
|
||||
root.addDeformer(new WaveDeformer("blink"));
|
||||
root.addDeformer(new WaveDeformer("wave"));
|
||||
root.addDeformer(new WaveDeformer("blink"));
|
||||
|
||||
// ==================== 设置元数据 ====================
|
||||
model.getMetadata().setAuthor("Test Author");
|
||||
model.getMetadata().setDescription("This is a full-featured test model with transforms and deformers.");
|
||||
model.getMetadata().setLicense("MIT");
|
||||
model.getMetadata().setFileFormatVersion("1.0.0");
|
||||
model.getMetadata().setUnitsPerMeter(100.0f);
|
||||
model.getMetadata().setProperty("custom_prop1", "value1");
|
||||
|
||||
// ==================== 添加物理 ====================
|
||||
PhysicsSystem physics = model.getPhysics();
|
||||
if (physics != null) {
|
||||
physics.initialize();
|
||||
PhysicsSystem.PhysicsParticle p1 = physics.addParticle("p1", new Vector2f(0, 0), 1f);
|
||||
PhysicsSystem.PhysicsParticle p2 = physics.addParticle("p2", new Vector2f(10, 0), 1f);
|
||||
physics.addSpring("spring1", p1, p2, 10f, 0.5f, 0.1f);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
public static void testModelSaveLoadIntegrity(Model2D model, String filePath) {
|
||||
System.out.println("\n--- Test: Model Save and Load Integrity ---");
|
||||
try {
|
||||
// 保存模型
|
||||
model.saveToFile(filePath);
|
||||
|
||||
// 加载模型
|
||||
Model2D loaded = Model2D.loadFromFile(filePath);
|
||||
|
||||
boolean integrityOk = true;
|
||||
|
||||
// ==================== 基本属性 ====================
|
||||
if (!model.getName().equals(loaded.getName())) {
|
||||
System.out.println("Name mismatch!");
|
||||
integrityOk = false;
|
||||
}
|
||||
if (!model.getVersion().equals(loaded.getVersion())) {
|
||||
System.out.println("Version mismatch!");
|
||||
integrityOk = false;
|
||||
}
|
||||
|
||||
// ==================== 部件 ====================
|
||||
if (model.getParts().size() != loaded.getParts().size()) {
|
||||
System.out.println("Parts count mismatch!");
|
||||
integrityOk = false;
|
||||
} else {
|
||||
for (int i = 0; i < model.getParts().size(); i++) {
|
||||
ModelPart orig = model.getParts().get(i);
|
||||
ModelPart loadPart = loaded.getParts().get(i);
|
||||
if (!orig.getName().equals(loadPart.getName())) {
|
||||
System.out.println("Part name mismatch: " + orig.getName());
|
||||
integrityOk = false;
|
||||
}
|
||||
// 检查变换
|
||||
if (!orig.getPosition().equals(loadPart.getPosition()) ||
|
||||
orig.getRotation() != loadPart.getRotation() ||
|
||||
!orig.getScale().equals(loadPart.getScale())) {
|
||||
System.out.println("Part transform mismatch: " + orig.getName());
|
||||
integrityOk = false;
|
||||
}
|
||||
// 检查Deformer
|
||||
if (orig.getDeformers().size() != loadPart.getDeformers().size()) {
|
||||
System.out.println("Deformer count mismatch on part: " + orig.getName());
|
||||
integrityOk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 网格 ====================
|
||||
if (model.getMeshes().size() != loaded.getMeshes().size()) {
|
||||
System.out.println("Meshes count mismatch!");
|
||||
integrityOk = false;
|
||||
}
|
||||
|
||||
// ==================== 纹理 ====================
|
||||
if (model.getTextures().size() != loaded.getTextures().size()) {
|
||||
System.out.println("Textures count mismatch!");
|
||||
integrityOk = false;
|
||||
}
|
||||
|
||||
// ==================== 参数 ====================
|
||||
if (model.getParameters().size() != loaded.getParameters().size()) {
|
||||
System.out.println("Parameters count mismatch!");
|
||||
integrityOk = false;
|
||||
} else {
|
||||
for (String key : model.getParameters().keySet()) {
|
||||
AnimationParameter origParam = model.getParameters().get(key);
|
||||
AnimationParameter loadParam = loaded.getParameters().get(key);
|
||||
if (origParam.getValue() != loadParam.getValue()) {
|
||||
System.out.println("Parameter value mismatch: " + key);
|
||||
integrityOk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 物理 ====================
|
||||
PhysicsSystem origPhysics = model.getPhysics();
|
||||
PhysicsSystem loadPhysics = loaded.getPhysics();
|
||||
if ((origPhysics != null && loadPhysics == null) || (origPhysics == null && loadPhysics != null)) {
|
||||
System.out.println("Physics system missing after load!");
|
||||
integrityOk = false;
|
||||
} else if (origPhysics != null) {
|
||||
if (origPhysics.getParticles().size() != loadPhysics.getParticles().size()) {
|
||||
System.out.println("Physics particle count mismatch!");
|
||||
integrityOk = false;
|
||||
}
|
||||
if (origPhysics.getSprings().size() != loadPhysics.getSprings().size()) {
|
||||
System.out.println("Physics spring count mismatch!");
|
||||
integrityOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Integrity test " + (integrityOk ? "PASSED" : "FAILED"));
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in testModelSaveLoadIntegrity: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void printModelState(Model2D model) {
|
||||
System.out.println(" - Name: " + model.getName());
|
||||
System.out.println(" - Version: " + model.getVersion());
|
||||
System.out.println(" - Parts: " + model.getParts().size());
|
||||
for (ModelPart part : model.getParts()) {
|
||||
printPartHierarchy(part, 1);
|
||||
}
|
||||
System.out.println(" - Parameters:");
|
||||
for (AnimationParameter param : model.getParameters().values()) {
|
||||
System.out.println(" * " + param.getId() + " = " + param.getValue());
|
||||
}
|
||||
System.out.println(" - Textures:");
|
||||
model.getTextures().forEach((k, tex) -> {
|
||||
System.out.println(" * " + tex.getName() + " (" + tex.getWidth() + "x" + tex.getHeight() + ")");
|
||||
});
|
||||
System.out.println(" - User Properties:");
|
||||
model.getMetadata().getUserProperties().forEach((k, v) ->
|
||||
System.out.println(" * " + k + ": " + v)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize OpenGL context for texture testing
|
||||
*/
|
||||
@@ -586,6 +808,8 @@ public class ModelTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to print part hierarchy
|
||||
*/
|
||||
|
||||
682
src/main/java/com/chuangzhou/vivid2D/test/ModelTest2.java
Normal file
682
src/main/java/com/chuangzhou/vivid2D/test/ModelTest2.java
Normal 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user