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 Ticketed randomizer node for BTs to more heavily weight attacking and waiting
+ bug fixes + bug fixes
Fix cursor always placing at origin
Fix jump/fall/land animations being buggy and inconsistent between client/server Fix jump/fall/land animations being buggy and inconsistent between client/server
Fix empty item slot not showing underneath dragged item Fix empty item slot not showing underneath dragged item
Fix grass rendering distance 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 Fix fall tree blocking attack starting on server/Fix falling tree not always deactivating on server
ParShapes integration fix ParShapes integration fix
Fix server ground movement tree playing animation over falling animation Fix server ground movement tree playing animation over falling animation
Fix cursor visuals
3D spline implementation
# TODO # TODO

View File

@ -1,5 +1,6 @@
package electrosphere.engine.loadingthreads; package electrosphere.engine.loadingthreads;
import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.joml.Vector3f; import org.joml.Vector3f;
@ -26,6 +27,8 @@ import electrosphere.menu.WindowUtils;
import electrosphere.menu.mainmenu.MenuGeneratorsMultiplayer; import electrosphere.menu.mainmenu.MenuGeneratorsMultiplayer;
import electrosphere.net.NetUtils; import electrosphere.net.NetUtils;
import electrosphere.net.client.ClientNetworking; import electrosphere.net.client.ClientNetworking;
import electrosphere.renderer.actor.Actor;
import electrosphere.renderer.actor.ActorTextureMask;
import electrosphere.renderer.ui.elements.Window; import electrosphere.renderer.ui.elements.Window;
public class ClientLoading { public class ClientLoading {
@ -246,8 +249,10 @@ public class ClientLoading {
//player's cursor //player's cursor
Globals.playerCursor = EntityCreationUtils.createClientSpatialEntity(); Globals.playerCursor = EntityCreationUtils.createClientSpatialEntity();
EntityCreationUtils.makeEntityDrawable(Globals.playerCursor, AssetDataStrings.UNITSPHERE); 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); 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; 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' * The threshold at which we say the colors are 'close enough'
*/ */
static final int COLOR_COMPARE_THRESHOLD = 3; 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 * Asserts that the most recent render matches the image stored at the provided filepath
@ -107,4 +112,17 @@ public class Assertions {
assertEventually(test, 100); 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)));
}
}