fall tree tests, testing utils, assertion macros
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2024-09-05 16:00:36 -04:00
parent 9cdfac7898
commit eff082ca76
11 changed files with 347 additions and 109 deletions

View File

@ -98,12 +98,15 @@ public class MovementAudioService {
* @return The path to the audio file to play
*/
public String getAudioPath(int voxelType, InteractionType type){
String rVal = null;
SurfaceAudioType surfaceAudio = this.defaultSurfaceAudio;
//Check if ignored
for(int ignoredVoxelType : this.ignoredVoxelTypes){
if(ignoredVoxelType == voxelType){
return null;
if(this.ignoredVoxelTypes != null){
for(int ignoredVoxelType : this.ignoredVoxelTypes){
if(ignoredVoxelType == voxelType){
return null;
}
}
}
@ -115,31 +118,33 @@ public class MovementAudioService {
}
//gets the list to pull from
List<String> availableFiles = surfaceAudio.getFootstepRegularBareAudioPaths();
switch(type){
case STEP_BARE_REG: {
availableFiles = surfaceAudio.getFootstepRegularBareAudioPaths();
} break;
case STEP_BARE_HEAVY: {
availableFiles = surfaceAudio.getFootstepHeavyBareAudioPaths();
} break;
case STEP_SHOE_REG: {
availableFiles = surfaceAudio.getFootstepRegularShoeAudioPaths();
} break;
case STEP_SHOE_HEAVY: {
availableFiles = surfaceAudio.getFootstepHeavyShoeAudioPaths();
} break;
case JUMP: {
availableFiles = surfaceAudio.getJumpAudioPaths();
} break;
case LAND: {
availableFiles = surfaceAudio.getLandAudioPaths();
} break;
if(surfaceAudio != null){
List<String> availableFiles = surfaceAudio.getFootstepRegularBareAudioPaths();
switch(type){
case STEP_BARE_REG: {
availableFiles = surfaceAudio.getFootstepRegularBareAudioPaths();
} break;
case STEP_BARE_HEAVY: {
availableFiles = surfaceAudio.getFootstepHeavyBareAudioPaths();
} break;
case STEP_SHOE_REG: {
availableFiles = surfaceAudio.getFootstepRegularShoeAudioPaths();
} break;
case STEP_SHOE_HEAVY: {
availableFiles = surfaceAudio.getFootstepHeavyShoeAudioPaths();
} break;
case JUMP: {
availableFiles = surfaceAudio.getJumpAudioPaths();
} break;
case LAND: {
availableFiles = surfaceAudio.getLandAudioPaths();
} break;
}
int roll = random.nextInt(availableFiles.size());
rVal = availableFiles.get(roll);
}
//return the audio
int roll = random.nextInt(availableFiles.size());
return availableFiles.get(roll);
return rVal;
}
/**

View File

@ -466,10 +466,13 @@ public class CollisionEngine {
DBody rigidBody = PhysicsEntityUtils.getDBody(physicsEntity);
Matrix4d inverseTransform = new Matrix4d();
Vector4d rawPos = inverseTransform.transform(new Vector4d(PhysicsUtils.getRigidBodyPosition(rigidBody),1));
Vector3d newPosition = new Vector3d(rawPos.x,rawPos.y,rawPos.z);
newPosition = this.suggestMovementPosition(collisionWorldData, newPosition);
Vector3d calculatedPosition = new Vector3d(rawPos.x,rawPos.y,rawPos.z);
Vector3d suggestedPosition = this.suggestMovementPosition(collisionWorldData, calculatedPosition);
if(calculatedPosition.distance(suggestedPosition) > 0){
collidable.addImpulse(new Impulse(new Vector3d(), new Vector3d(), new Vector3d(), 0, Collidable.TYPE_WORLD_BOUND));
}
Quaterniond newRotation = PhysicsUtils.getRigidBodyRotation(rigidBody);
EntityUtils.getPosition(physicsEntity).set(newPosition);
EntityUtils.getPosition(physicsEntity).set(suggestedPosition);
EntityUtils.getRotation(physicsEntity).set(newRotation);
}
}

View File

@ -47,6 +47,9 @@ public class Collidable {
public static final String TYPE_FOLIAGE_STATIC = "foliageStatic";
public static final long TYPE_FOLIAGE_BIT = 0x80;
public static final String TYPE_WORLD_BOUND = "worldBound";
public static final long TYPE_WORLD_BOUND_BIT = 0x100;
/**
* Constructor

View File

@ -44,29 +44,23 @@ public class ServerCollidableTree implements BehaviorTree {
public void simulate(float deltaTime){
Vector3d position = EntityUtils.getPosition(parent);
Quaterniond rotation = EntityUtils.getRotation(parent);
Vector3d offsetVector = new Vector3d();
Vector3d newPosition = new Vector3d(position);
//have we hit a terrain impulse?
boolean hitTerrain = false;
//handle impulses
for(Impulse impulse : collidable.getImpulses()){
// collidable.getImpulses().remove(impulse);
if(impulse.type.matches(Collidable.TYPE_TERRAIN)){
hitTerrain = true;
// System.out.println("Impulse force: " + impulseForce);
// System.out.println("Position: " + position);
}
if(impulse.type.matches(Collidable.TYPE_ITEM)){
if(ServerGravityTree.getServerGravityTree(parent)!=null){
ServerGravityTree.getServerGravityTree(parent).start();
}
}
if(impulse.type.matches(Collidable.TYPE_CREATURE)){
// System.out.println(System.currentTimeMillis() + " creature hit!");
if(ServerGravityTree.getServerGravityTree(parent)!=null){
ServerGravityTree.getServerGravityTree(parent).start();
}
}
if(impulse.type.matches(Collidable.TYPE_WORLD_BOUND)){
this.resetGravityFall();
}
}
Realm realm = Globals.realmManager.getEntityRealm(parent);
//bound to world bounds
@ -101,5 +95,17 @@ public class ServerCollidableTree implements BehaviorTree {
return (ServerCollidableTree)e.getData(EntityDataStrings.SERVER_COLLIDABLE_TREE);
}
/**
* Adds a terrain collision to the collidable list
*/
protected void resetGravityFall(){
if(ServerGravityTree.getServerGravityTree(parent)!=null){
ServerGravityTree.getServerGravityTree(parent).stop();
}
if(ServerFallTree.getFallTree(parent)!=null){
ServerFallTree.getFallTree(parent).land();
}
}
}

View File

@ -63,6 +63,10 @@ public class ServerGravityTree implements BehaviorTree {
if(state == GravityTreeState.NOT_ACTIVE){
frameCurrent = 0;
}
ServerFallTree fallTree;
if((fallTree = ServerFallTree.getFallTree(parent))!=null){
fallTree.start();
}
}
public void interrupt(){

View File

@ -0,0 +1,61 @@
package electrosphere.entity.state.movement.fall;
import static org.junit.jupiter.api.Assertions.*;
import org.joml.Vector3d;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.state.movement.jump.ClientJumpTree;
import electrosphere.entity.types.creature.CreatureTemplate;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.test.annotations.IntegrationTest;
import electrosphere.test.template.EntityTestTemplate;
import electrosphere.test.testutils.TestEngineUtils;
import static electrosphere.test.testutils.Assertions.*;
/**
* Tests the fall tree
*/
public class ClientFallTreeTests extends EntityTestTemplate {
@IntegrationTest
public void isFalling_AtRest_false(){
Entity serverEntity = CreatureUtils.serverSpawnBasicCreature(Globals.realmManager.first(), new Vector3d(), "human", CreatureTemplate.createDefault("human"));
Entity clientEntity = TestEngineUtils.getClientEquivalent(serverEntity);
ClientFallTree clientFallTree = ClientFallTree.getFallTree(clientEntity);
assertEquals(false, clientFallTree.isFalling());
}
@IntegrationTest
public void isFalling_AfterJump_true(){
Entity serverEntity = CreatureUtils.serverSpawnBasicCreature(Globals.realmManager.first(), new Vector3d(), "human", CreatureTemplate.createDefault("human"));
Entity clientEntity = TestEngineUtils.getClientEquivalent(serverEntity);
ClientFallTree clientFallTree = ClientFallTree.getFallTree(clientEntity);
ClientJumpTree clientJumpTree = ClientJumpTree.getClientJumpTree(clientEntity);
clientJumpTree.start();
//make sure we're in in the air
assertEventually(() -> clientFallTree.isFalling(), 100);
}
@IntegrationTest
public void isFalling_AfterLand_false(){
Entity serverEntity = CreatureUtils.serverSpawnBasicCreature(Globals.realmManager.first(), new Vector3d(), "human", CreatureTemplate.createDefault("human"));
Entity clientEntity = TestEngineUtils.getClientEquivalent(serverEntity);
ClientFallTree clientFallTree = ClientFallTree.getFallTree(clientEntity);
ClientJumpTree clientJumpTree = ClientJumpTree.getClientJumpTree(clientEntity);
clientJumpTree.start();
//make sure we're in in the air
TestEngineUtils.simulateFrames(3);
assertEventually(() -> {
return !clientFallTree.isFalling();
});
}
}

View File

@ -0,0 +1,61 @@
package electrosphere.entity.state.movement.fall;
import static org.junit.jupiter.api.Assertions.*;
import org.joml.Vector3d;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.state.movement.jump.ServerJumpTree;
import electrosphere.entity.types.creature.CreatureTemplate;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.test.annotations.IntegrationTest;
import electrosphere.test.template.EntityTestTemplate;
import static electrosphere.test.testutils.Assertions.*;
import electrosphere.test.testutils.TestEngineUtils;
/**
* Tests the server fall tree
*/
public class ServerFallTreeTests extends EntityTestTemplate {
@IntegrationTest
public void isFalling_AtRest_false(){
Entity creature = CreatureUtils.serverSpawnBasicCreature(Globals.realmManager.first(), new Vector3d(0,0,0), "human", CreatureTemplate.createDefault("human"));
ServerFallTree serverFallTree = ServerFallTree.getFallTree(creature);
assertEquals(false, serverFallTree.isFalling());
}
@IntegrationTest
public void isFalling_AfterJump_true(){
Entity creature = CreatureUtils.serverSpawnBasicCreature(Globals.realmManager.first(), new Vector3d(0,0,0), "human", CreatureTemplate.createDefault("human"));
ServerFallTree serverFallTree = ServerFallTree.getFallTree(creature);
ServerJumpTree serverJumpTree = ServerJumpTree.getServerJumpTree(creature);
serverJumpTree.start();
//make sure we're in in the air
TestEngineUtils.waitForCondition(() -> !serverJumpTree.isJumping(), 100);
assertEquals(true, serverFallTree.isFalling());
}
@IntegrationTest
public void isFalling_AfterLand_false(){
Entity creature = CreatureUtils.serverSpawnBasicCreature(Globals.realmManager.first(), new Vector3d(0,0,0), "human", CreatureTemplate.createDefault("human"));
ServerFallTree serverFallTree = ServerFallTree.getFallTree(creature);
ServerJumpTree serverJumpTree = ServerJumpTree.getServerJumpTree(creature);
serverJumpTree.start();
//make sure we're in in the air
TestEngineUtils.simulateFrames(3);
assertEventually(() -> {
return !serverFallTree.isFalling();
});
}
}

View File

@ -6,6 +6,7 @@ import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
import electrosphere.test.template.extensions.RenderingExtension;
import static electrosphere.test.testutils.Assertions.*;
import electrosphere.test.testutils.TestRenderingUtils;
/**
@ -26,7 +27,7 @@ public class RenderingTestTemplate {
String canonicalName = this.getClass().getCanonicalName();
//check the render
TestRenderingUtils.assertEqualsRender(existingRenderPath, () -> {
assertEqualsRender(existingRenderPath, () -> {
//on failure, save the failed render
String failureSavePath = "./.testcache/" + canonicalName + "-" + renderName + ".png";

View File

@ -0,0 +1,110 @@
package electrosphere.test.testutils;
import static org.junit.jupiter.api.Assertions.*;
import java.io.File;
import java.io.IOException;
import java.util.function.Supplier;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import electrosphere.engine.Globals;
/**
* Custom assertion macros
*/
public class Assertions {
/**
* The threshold at which we say the colors are 'close enough'
*/
static final int COLOR_COMPARE_THRESHOLD = 3;
/**
* Asserts that the most recent render matches the image stored at the provided filepath
* @param existingRenderPath The filepath of the existing render
*/
public static void assertEqualsRender(String existingRenderPath, Runnable onFailure){
BufferedImage testData = null;
try {
testData = ImageIO.read(new File(existingRenderPath));
} catch (IOException e){
fail("Failed to read existing image path " + existingRenderPath);
}
BufferedImage screenshot = Globals.renderingEngine.defaultFramebuffer.getPixels(Globals.renderingEngine.getOpenGLState());
//check basic data
//
//width
if(testData.getWidth() != screenshot.getWidth()){
onFailure.run();
}
assertEquals(testData.getWidth(), screenshot.getWidth());
//
//height
if(testData.getHeight() != screenshot.getHeight()){
onFailure.run();
}
assertEquals(testData.getHeight(), screenshot.getHeight());
//
//pixel-by-pixel check
//
for(int x = 0; x < testData.getWidth(); x++){
for(int y = 0; y < testData.getHeight(); y++){
//get from-disk rgba
int sourceRed = testData.getRGB(x, y) & 0xff;
int sourceGreen = (testData.getRGB(x, y) & 0xff00) >> 8;
int sourceBlue = (testData.getRGB(x, y) & 0xff0000) >> 16;
int sourceAlpha = (testData.getRGB(x, y) & 0xff000000) >>> 24;
//get from-render rgba
int renderRed = screenshot.getRGB(x, y) & 0xff;
int renderGreen = (screenshot.getRGB(x, y) & 0xff00) >> 8;
int renderBlue = (screenshot.getRGB(x, y) & 0xff0000) >> 16;
int renderAlpha = (screenshot.getRGB(x, y) & 0xff000000) >>> 24;
if(
Math.abs(sourceRed - renderRed) > COLOR_COMPARE_THRESHOLD ||
Math.abs(sourceGreen - renderGreen) > COLOR_COMPARE_THRESHOLD ||
Math.abs(sourceBlue - renderBlue) > COLOR_COMPARE_THRESHOLD ||
Math.abs(sourceAlpha - renderAlpha) > COLOR_COMPARE_THRESHOLD
){
onFailure.run();
String failMessage = "Colors aren't approximately the same!\n" +
"Color from disk: " + sourceRed + "," + sourceGreen + "," + sourceBlue + "," + sourceAlpha + "\n" +
"Color from render: " + renderRed + "," + renderGreen + "," + renderBlue + "," + renderAlpha + "\n"
;
fail(failMessage);
}
}
}
}
/**
* Asserts that some test is true within a given number of frame simulations
* @param test The test
* @param maxFrames The number of frames
*/
public static void assertEventually(Supplier<Boolean> test, int maxFrames){
int frameCount = 0;
boolean testResult = false;
while(!(testResult = test.get()) && frameCount < maxFrames){
TestEngineUtils.simulateFrames(1);
frameCount++;
}
org.junit.jupiter.api.Assertions.assertTrue(testResult);
}
/**
* Asserts that some runnable
* @param test
*/
public static void assertEventually(Supplier<Boolean> test){
assertEventually(test, 100);
}
}

View File

@ -1,8 +1,13 @@
package electrosphere.test.testutils;
import static org.junit.jupiter.api.Assertions.*;
import java.util.function.Supplier;
import electrosphere.engine.Globals;
import electrosphere.engine.Main;
import electrosphere.engine.profiler.Profiler;
import electrosphere.entity.Entity;
import electrosphere.net.NetUtils;
/**
@ -57,4 +62,53 @@ public class TestEngineUtils {
}
}
/**
* The maximum number of frames to wait before an entity propagates to the client
*/
static final int MAX_FRAMES_TO_WAIT_FOR_CLIENT_PROPAGATION = 10;
/**
* Gets the client equivalent of an entity
* @param serverEntity The server entity
* @return The client entity
*/
public static Entity getClientEquivalent(Entity serverEntity){
int frames = 0;
while(frames < MAX_FRAMES_TO_WAIT_FOR_CLIENT_PROPAGATION && !Globals.clientSceneWrapper.containsServerId(serverEntity.getId())){
TestEngineUtils.simulateFrames(1);
}
if(Globals.clientSceneWrapper.containsServerId(serverEntity.getId())){
Entity rVal = Globals.clientSceneWrapper.getEntityFromServerId(serverEntity.getId());
if(rVal == null){
fail("Failed to find client entity at server id lookup for id: " + serverEntity.getId());
}
return rVal;
}
fail("Failed to find client entity at server id lookup for id: " + serverEntity.getId());
return null;
}
/**
* Waits for a given test to be true
* @param test The test to wait for
*/
public static void waitForCondition(Supplier<Boolean> test, int maxFrames){
int frameCount = 0;
boolean testResult = false;
while(!(testResult = test.get()) && frameCount < maxFrames){
TestEngineUtils.simulateFrames(1);
frameCount++;
}
org.junit.jupiter.api.Assertions.assertTrue(testResult);
}
/**
* Waits for a given test to be true
* @param test The test to wait for
*/
public static void waitForCondition(Supplier<Boolean> test){
waitForCondition(test,100);
}
}

View File

@ -6,8 +6,6 @@ import java.io.IOException;
import javax.imageio.ImageIO;
import static org.junit.jupiter.api.Assertions.*;
import electrosphere.engine.Globals;
/**
@ -15,74 +13,6 @@ import electrosphere.engine.Globals;
*/
public class TestRenderingUtils {
/**
* The threshold at which we say the colors are 'close enough'
*/
static final int COLOR_COMPARE_THRESHOLD = 3;
/**
* Asserts that the most recent render matches the image stored at the provided filepath
* @param existingRenderPath The filepath of the existing render
*/
public static void assertEqualsRender(String existingRenderPath, Runnable onFailure){
BufferedImage testData = null;
try {
testData = ImageIO.read(new File(existingRenderPath));
} catch (IOException e){
fail("Failed to read existing image path " + existingRenderPath);
}
BufferedImage screenshot = Globals.renderingEngine.defaultFramebuffer.getPixels(Globals.renderingEngine.getOpenGLState());
//check basic data
//
//width
if(testData.getWidth() != screenshot.getWidth()){
onFailure.run();
}
assertEquals(testData.getWidth(), screenshot.getWidth());
//
//height
if(testData.getHeight() != screenshot.getHeight()){
onFailure.run();
}
assertEquals(testData.getHeight(), screenshot.getHeight());
//
//pixel-by-pixel check
//
for(int x = 0; x < testData.getWidth(); x++){
for(int y = 0; y < testData.getHeight(); y++){
//get from-disk rgba
int sourceRed = testData.getRGB(x, y) & 0xff;
int sourceGreen = (testData.getRGB(x, y) & 0xff00) >> 8;
int sourceBlue = (testData.getRGB(x, y) & 0xff0000) >> 16;
int sourceAlpha = (testData.getRGB(x, y) & 0xff000000) >>> 24;
//get from-render rgba
int renderRed = screenshot.getRGB(x, y) & 0xff;
int renderGreen = (screenshot.getRGB(x, y) & 0xff00) >> 8;
int renderBlue = (screenshot.getRGB(x, y) & 0xff0000) >> 16;
int renderAlpha = (screenshot.getRGB(x, y) & 0xff000000) >>> 24;
if(
Math.abs(sourceRed - renderRed) > COLOR_COMPARE_THRESHOLD ||
Math.abs(sourceGreen - renderGreen) > COLOR_COMPARE_THRESHOLD ||
Math.abs(sourceBlue - renderBlue) > COLOR_COMPARE_THRESHOLD ||
Math.abs(sourceAlpha - renderAlpha) > COLOR_COMPARE_THRESHOLD
){
onFailure.run();
String failMessage = "Colors aren't approximately the same!\n" +
"Color from disk: " + sourceRed + "," + sourceGreen + "," + sourceBlue + "," + sourceAlpha + "\n" +
"Color from render: " + renderRed + "," + renderGreen + "," + renderBlue + "," + renderAlpha + "\n"
;
fail(failMessage);
}
}
}
}
/**
* Used for saving a copy of the current render (ie for generating test data)
* @param existingRenderPath The filepath of the existing render