3d spline implementation
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-09-10 01:32:21 -04:00
parent 55eacf2c1b
commit 706d25de56
6 changed files with 550 additions and 2 deletions

View File

@ -15,7 +15,6 @@
Ticketed randomizer node for BTs to more heavily weight attacking and waiting
+ bug fixes
Fix cursor always placing at origin
Fix jump/fall/land animations being buggy and inconsistent between client/server
Fix empty item slot not showing underneath dragged item
Fix grass rendering distance

View File

@ -710,6 +710,8 @@ Movement speed penalty on swinging sword
Fix fall tree blocking attack starting on server/Fix falling tree not always deactivating on server
ParShapes integration fix
Fix server ground movement tree playing animation over falling animation
Fix cursor visuals
3D spline implementation
# TODO

View File

@ -1,5 +1,6 @@
package electrosphere.engine.loadingthreads;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.joml.Vector3f;
@ -26,6 +27,8 @@ import electrosphere.menu.WindowUtils;
import electrosphere.menu.mainmenu.MenuGeneratorsMultiplayer;
import electrosphere.net.NetUtils;
import electrosphere.net.client.ClientNetworking;
import electrosphere.renderer.actor.Actor;
import electrosphere.renderer.actor.ActorTextureMask;
import electrosphere.renderer.ui.elements.Window;
public class ClientLoading {
@ -246,8 +249,10 @@ public class ClientLoading {
//player's cursor
Globals.playerCursor = EntityCreationUtils.createClientSpatialEntity();
EntityCreationUtils.makeEntityDrawable(Globals.playerCursor, AssetDataStrings.UNITSPHERE);
Actor cursorActor = EntityUtils.getActor(Globals.playerCursor);
cursorActor.addTextureMask(new ActorTextureMask("sphere", Arrays.asList(new String[]{"Textures/transparent_red.png"})));
DrawableUtils.makeEntityTransparent(Globals.playerCursor);
EntityUtils.getScale(Globals.playerCursor).set(30f);
EntityUtils.getScale(Globals.playerCursor).set(0.2f);
}
static final int MAX_DRAW_CELL_WAIT = 1000;

View File

@ -0,0 +1,266 @@
package electrosphere.util.ds;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.joml.Matrix4d;
import org.joml.Vector3d;
import org.joml.Vector4d;
/**
* An implementation of a 3d Catmull-Rom spline
*/
public class Spline3d {
/**
* The points to interpolate between when calculating the spline
*/
TreeMap<Double,Vector4d> pointTree;
/**
* The characteristic matrix for this spline
*/
Matrix4d characteristicMatrix;
/**
* The type of spline to create
*/
public static enum Spline3dType {
/**
* A catmull-rom spline
*/
CATMULL_ROM,
/**
* A bezier spline
*/
BEZIER,
/**
* A basis spline (b-spline)
*/
B,
}
/**
* Creates a spline with no points
*/
public Spline3d(Spline3dType type){
this.setCharacteristicMatrix(type);
pointTree = new TreeMap<Double,Vector4d>();
}
/**
* Creates a spline with predefined points
* @param points The points
*/
public Spline3d(Spline3dType type, List<Vector3d> points){
this.setCharacteristicMatrix(type);
pointTree = new TreeMap<Double,Vector4d>();
int i = 0;
for(Vector3d point : points){
pointTree.put((double)i,new Vector4d(point,1.0));
i++;
}
}
/**
* Creates a spline with predefined points
* @param points The points
*/
public Spline3d(Spline3dType type, Vector3d[] points){
this.setCharacteristicMatrix(type);
pointTree = new TreeMap<Double,Vector4d>();
int i = 0;
for(Vector3d point : points){
pointTree.put((double)i,new Vector4d(point,1.0));
i++;
}
}
/**
* Creates a catmull-rom spline
* @return The spline
*/
public static Spline3d createCatmullRom(){
return new Spline3d(Spline3dType.CATMULL_ROM);
}
/**
* Creates a catmull-rom spline
* @param points The points to create the spline with
* @return The spline
*/
public static Spline3d createCatmullRom(Vector3d[] points){
return new Spline3d(Spline3dType.CATMULL_ROM, points);
}
/**
* Gets the position of a spline at a given position
* @param t The time into the spline
* @return The position along the spline at that position
*/
public Vector3d getPos(double t){
if(t < 0){
throw new ArrayIndexOutOfBoundsException("Tried to get value outside of spline bounds! " + t);
}
if(t >= pointTree.size()){
throw new ArrayIndexOutOfBoundsException("Tried to get value outside of spline bounds! " + t + " " + pointTree.size());
}
if(pointTree.size() < 2){
throw new ArrayIndexOutOfBoundsException("Trying to get value along spline that only has " + pointTree.size() + " points!");
}
if(pointTree.floorEntry(t).getKey() == pointTree.lastEntry().getKey()){
throw new ArrayIndexOutOfBoundsException("Trying to get value along spline after last control point! " + t + " " + pointTree.lastEntry().getKey() + " " + pointTree.size());
}
if(pointTree.ceilingEntry(t).getKey() == pointTree.firstEntry().getKey()){
throw new ArrayIndexOutOfBoundsException("Trying to get value along spline before first control point! " + t + " " + pointTree.firstEntry().getKey() + " " + pointTree.size());
}
//get middle two points
Vector4d p2 = pointTree.floorEntry(t).getValue();
Vector4d p3 = pointTree.ceilingEntry(t).getValue();
//
//Get control points for current segment
Vector4d p1;
if(pointTree.floorKey(t) <= 0){
//if we're at the beginning of the line, extrapolate from p2 based on the direction of p3
p1 = new Vector4d(p2).add(new Vector4d(p2).sub(new Vector4d(p3)));
} else {
//else, use the point before p2
p1 = pointTree.floorEntry(t-1).getValue();
}
Vector4d p4;
if(pointTree.ceilingKey(t) >= pointTree.size() - 1){
//if we're at the end of the line, extrapolate from p3 based on the direction of p2
p4 = new Vector4d(p3).add(new Vector4d(p3).sub(new Vector4d(p2)));
} else {
//else, use the point after p3
p4 = pointTree.ceilingEntry(t+1).getValue();
}
//t normalized to range [0,1]
double tN = t - pointTree.floorKey(t);
//
//get the time vec
Vector4d timeVec = new Vector4d(1.0, tN, tN*tN, tN*tN*tN);
//
//Perform calculation
Matrix4d pointMat = new Matrix4d(p1,p2,p3,p4).transpose();
Matrix4d matResult = characteristicMatrix.mul(pointMat);
Vector4d rawResult = timeVec.mul(matResult.transpose());
Vector4d normalizedResult = rawResult;
if(rawResult.w != 0 && Double.isFinite(rawResult.w)){
normalizedResult = rawResult.div(rawResult.w);
}
return new Vector3d(normalizedResult.x,normalizedResult.y,normalizedResult.z);
}
/**
* Gets the list of control points contained in the spline
* @return The list of control points contained in the spline
*/
public List<Vector3d> getPoints(){
return this.pointTree.values().stream().map((vec) -> new Vector3d(vec.x,vec.y,vec.z)).collect(Collectors.toList());
}
/**
* Adds a control point to the spline
* @param controlPoint The control point to add
*/
public void addPoint(Vector3d controlPoint){
for(Vector3d existingPoint : this.getPoints()){
if(existingPoint.equals(controlPoint)){
throw new IllegalArgumentException("Tried adding existing control point to spline!");
}
}
this.pointTree.put((double)pointTree.size(),new Vector4d(controlPoint,1.0));
}
/**
* Checks if the spline contains a given control point
* @param controlPoint The control point to check for
* @return true if the point is contained, false otherwise
*/
public boolean containsPoint(Vector3d controlPoint){
for(Vector3d existingPoint : this.getPoints()){
if(existingPoint.equals(controlPoint)){
return true;
}
}
return false;
}
/**
* Removes a control point from the spline
* @param controlPoint The control point to remove
*/
public void removePoint(Vector3d controlPoint){
List<Vector3d> pointsToSet = this.pointTree.values().stream().map(v -> new Vector3d(v.x,v.y,v.z)).filter(v -> !v.equals(controlPoint)).collect(Collectors.toList());
this.pointTree.clear();
int i = 0;
for(Vector3d point : pointsToSet){
this.pointTree.put((double)i,new Vector4d(point,1.0));
i++;
}
}
/**
* Sets the characteristic matrix of the spline
* @param type The type of characteristic to use
*/
private void setCharacteristicMatrix(Spline3dType type){
switch(type){
case CATMULL_ROM: {
setAsCatmullRom();
} break;
case BEZIER: {
setAsBezier();
} break;
case B: {
setAsBasisSpline();
} break;
}
}
/**
* Sets this spline to use the catmull-rom characteristic matrix
*/
private void setAsCatmullRom(){
this.characteristicMatrix = new Matrix4d(
0, 1, 0, 0,
-0.5, 0, 0.5, 0,
1, -5.0/2.0, 2, -0.5,
-0.5, 1.5, -1.5, 0.5
).transpose();
}
/**
* Sets this spline to use the bezier characteristic matrix
*/
private void setAsBezier(){
this.characteristicMatrix = new Matrix4d(
1, 0, 0, 0,
-3, 3, 0, 0,
3, -6, 3, 0,
-1, 3, -3, 1
).transpose();
}
/**
* Sets this spline to use the b-spline (basis spline) characteristic matrix
*/
private void setAsBasisSpline(){
this.characteristicMatrix = new Matrix4d(
1, 4, 1, 0,
-3, 0, 3, 0,
3, -6, 3, 0,
-1, 3, -3, 1
).transpose();
}
}

View File

@ -20,6 +20,11 @@ public class Assertions {
* The threshold at which we say the colors are 'close enough'
*/
static final int COLOR_COMPARE_THRESHOLD = 3;
/**
* A very small number for comparisons
*/
static final double VERY_SMALL_NUMBER = 0.0001;
/**
* Asserts that the most recent render matches the image stored at the provided filepath
@ -107,4 +112,17 @@ public class Assertions {
assertEventually(test, 100);
}
/**
* Asserts that two numbers are very close
* @param d1
* @param d2
*/
public static void assertVeryClose(double d1, double d2){
if(Math.abs(d1 - d2) < VERY_SMALL_NUMBER){
assertTrue(true);
} else {
fail("Values not close! " + d1 + " vs " + d2);
}
}
}

View File

@ -0,0 +1,258 @@
package electrosphere.util.ds;
import static org.junit.jupiter.api.Assertions.*;
import static electrosphere.test.testutils.Assertions.*;
import org.joml.Vector3d;
import org.junit.jupiter.api.Test;
import electrosphere.util.ds.Spline3d.Spline3dType;
/**
* Tests for the spline 3d implementation
*/
public class Spline3dTests {
@Test
public void testBasicLine1(){
double t = 1.5;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,0),
new Vector3d(3,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLine2(){
double t = 1.99;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,0),
new Vector3d(3,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLine3(){
double t = 1.01;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,0),
new Vector3d(3,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicYCurve1(){
double t = 1.5;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,1,0),
new Vector3d(3,1,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0.5,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicYCurve2(){
double t = 1.01;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,1,0),
new Vector3d(3,1,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertVeryClose(0.00515,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicYCurve3(){
double t = 1.99;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,1,0),
new Vector3d(3,1,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertVeryClose(0.9948,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicZCurve1(){
double t = 1.5;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,1),
new Vector3d(3,0,1),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0.5,midpoint.z);
}
@Test
public void testBasicZCurve2(){
double t = 1.01;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,1),
new Vector3d(3,0,1),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0,midpoint.y);
assertVeryClose(0.00515,midpoint.z);
}
@Test
public void testBasicZCurve3(){
double t = 1.99;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,1),
new Vector3d(3,0,1),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(t,midpoint.x);
assertEquals(0,midpoint.y);
assertVeryClose(0.9948,midpoint.z);
}
@Test
public void testBasicLineMissingFirst1(){
double t = 0.5;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(1,0,0),
new Vector3d(2,0,0),
new Vector3d(3,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(1.5,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLineMissingFirst2(){
double t = 0.99;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(1,0,0),
new Vector3d(2,0,0),
new Vector3d(3,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(1.99,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLineMissingFirst3(){
double t = 0.01;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(1,0,0),
new Vector3d(2,0,0),
new Vector3d(3,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(1.01,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLineMissingLast1(){
double t = 1.5;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(1.5,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLineMissingLast2(){
double t = 1.99;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(1.99,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testBasicLineMissingLast3(){
double t = 1.01;
Spline3d spline = new Spline3d(Spline3dType.CATMULL_ROM, new Vector3d[]{
new Vector3d(0,0,0),
new Vector3d(1,0,0),
new Vector3d(2,0,0),
});
Vector3d midpoint = spline.getPos(t);
assertEquals(1.01,midpoint.x);
assertEquals(0,midpoint.y);
assertEquals(0,midpoint.z);
}
@Test
public void testCanAddPoint(){
Spline3d spline = Spline3d.createCatmullRom();
spline.addPoint(new Vector3d(0,0,0));
assertEquals(1, spline.getPoints().size());
}
@Test
public void testCanRemovePoint(){
Spline3d spline = Spline3d.createCatmullRom();
spline.addPoint(new Vector3d(0,0,0));
spline.removePoint(new Vector3d(0,0,0));
assertEquals(0, spline.getPoints().size());
}
@Test
public void testCanContainsCheckPoint(){
Spline3d spline = Spline3d.createCatmullRom();
spline.addPoint(new Vector3d(0,0,0));
assertTrue(spline.containsPoint(new Vector3d(0,0,0)));
}
}