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 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 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"); } }