diff --git a/config.txt b/config.txt
index 08c5e328..c105569a 100644
--- a/config.txt
+++ b/config.txt
@@ -29,12 +29,12 @@ Player IDs : true
# Networking Settings
Auto-Connect : true
Auto-Connect Delay : 1000
-Default Server : localhost:3200
+Default Server : localhost:60001
Drawing Port : 32769
Monitor Step : 0.04
Network Buffer : false
# To store more servers, just insert more "Server" lines
-Server : localhost:3200
+Server : localhost:60001
# General Settings
Record Logfiles : false
diff --git a/jsgl/src/main/java/jsgl/jogl/model/ObjMaterial.java b/jsgl/src/main/java/jsgl/jogl/model/ObjMaterial.java
index b0702a01..1b4fe2cb 100644
--- a/jsgl/src/main/java/jsgl/jogl/model/ObjMaterial.java
+++ b/jsgl/src/main/java/jsgl/jogl/model/ObjMaterial.java
@@ -41,6 +41,7 @@ public class ObjMaterial extends MeshMaterial
protected int illum = 1;
protected Texture2D texture = null;
private InputStream textureSource = null;
+ private BufferedImage textureImage = null;
private boolean autoDisposeTexture = true;
private boolean useMipMaps = false;
@@ -78,11 +79,14 @@ public ObjMaterial(String name)
@Override
public void init(GL2 gl)
{
- if (textureSource == null)
+ if (textureSource == null && textureImage == null)
return;
try {
- BufferedImage img = ImageIO.read(textureSource);
+ BufferedImage img = textureImage;
+ if (img == null) {
+ img = ImageIO.read(textureSource);
+ }
if (img != null) {
if (useMipMaps)
texture = Texture2D.loadTexMipmaps(gl, new GLU(), img);
@@ -164,6 +168,11 @@ public void readTextureMap(InputStream texSrc)
textureSource = texSrc;
}
+ public void setTextureImage(BufferedImage textureImage)
+ {
+ this.textureImage = textureImage;
+ }
+
@Override
public void apply(GL2 gl)
{
diff --git a/jsgl/src/main/java/jsgl/jogl/model/StdMeshImporter.java b/jsgl/src/main/java/jsgl/jogl/model/StdMeshImporter.java
new file mode 100644
index 00000000..90f92f2a
--- /dev/null
+++ b/jsgl/src/main/java/jsgl/jogl/model/StdMeshImporter.java
@@ -0,0 +1,186 @@
+package jsgl.jogl.model;
+
+import jsgl.math.BoundingBox;
+import jsgl.math.geom.GeodesicSphere;
+import jsgl.math.vector.Vec3f;
+
+public class StdMeshImporter
+{
+ public static final String UNIT_SPHERE_NAME = "StdUnitSphere";
+ public static final String UNIT_BOX_NAME = "StdUnitBox";
+ public static final String CAPSULE_NAME = "StdCapsule";
+ public static final String UNIT_CYLINDER_NAME = "StdUnitCylinder";
+
+ public static Mesh generate(String name)
+ {
+ return switch (name) {
+ case UNIT_SPHERE_NAME -> generateUnitSphere();
+ case UNIT_BOX_NAME -> generateUnitBox();
+ case CAPSULE_NAME -> generateCapsule();
+ case UNIT_CYLINDER_NAME -> generateUnitCylinder();
+ default -> null;
+ };
+ }
+
+ private static Mesh generateUnitSphere()
+ {
+ final var sphere = new GeodesicSphere(1, 4);
+ final Mesh mesh = new Mesh();
+ final MeshPart part = new MeshPart();
+ for (var v : sphere.getVerts()) {
+ mesh.addVertex(new MeshVertex(v, v, null));
+ }
+ for (var t : sphere.getTriangles()) {
+ part.addFace(new MeshFace(t));
+ }
+ part.setMaterial(new ObjMaterial("Default"));
+ mesh.addPart(part);
+ mesh.setBounds(new BoundingBox(new Vec3f(-0.5f, -0.5f, -0.5f), new Vec3f(0.5f, 0.5f, 0.5f)));
+ return mesh;
+ }
+
+ private static Mesh generateUnitBox()
+ {
+ final Mesh mesh = new Mesh();
+ final MeshPart part = new MeshPart();
+
+ final var n1 = new float[] {0, 0, -1};
+ final var n2 = new float[] {0, -1, 0};
+ final var n3 = new float[] {1, 0, 0};
+ final var n4 = new float[] {0, 1, 0};
+ final var n5 = new float[] {-1, 0, 0};
+ final var n6 = new float[] {0, 0, 1};
+
+ final var v1 = new MeshVertex(new float[] {-0.5f, -0.5f, -0.5f}, n1, null);
+ final var v2 = new MeshVertex(new float[] {0.5f, -0.5f, -0.5f}, n1, null);
+ final var v3 = new MeshVertex(new float[] {0.5f, 0.5f, -0.5f}, n1, null);
+ final var v4 = new MeshVertex(new float[] {-0.5f, 0.5f, -0.5f}, n1, null);
+ final var v5 = new MeshVertex(new float[] {-0.5f, -0.5f, 0.5f}, n6, null);
+ final var v6 = new MeshVertex(new float[] {0.5f, -0.5f, 0.5f}, n6, null);
+ final var v7 = new MeshVertex(new float[] {0.5f, 0.5f, 0.5f}, n6, null);
+ final var v8 = new MeshVertex(new float[] {-0.5f, 0.5f, 0.5f}, n6, null);
+ final var v9 = new MeshVertex(new float[] {-0.5f, -0.5f, -0.5f}, n2, null);
+ final var v10 = new MeshVertex(new float[] {0.5f, -0.5f, -0.5f}, n2, null);
+ final var v11 = new MeshVertex(new float[] {0.5f, 0.5f, -0.5f}, n4, null);
+ final var v12 = new MeshVertex(new float[] {-0.5f, 0.5f, -0.5f}, n4, null);
+ final var v13 = new MeshVertex(new float[] {-0.5f, -0.5f, 0.5f}, n2, null);
+ final var v14 = new MeshVertex(new float[] {0.5f, -0.5f, 0.5f}, n2, null);
+ final var v15 = new MeshVertex(new float[] {0.5f, 0.5f, 0.5f}, n4, null);
+ final var v16 = new MeshVertex(new float[] {-0.5f, 0.5f, 0.5f}, n4, null);
+ final var v17 = new MeshVertex(new float[] {-0.5f, -0.5f, -0.5f}, n5, null);
+ final var v18 = new MeshVertex(new float[] {0.5f, -0.5f, -0.5f}, n3, null);
+ final var v19 = new MeshVertex(new float[] {0.5f, 0.5f, -0.5f}, n3, null);
+ final var v20 = new MeshVertex(new float[] {-0.5f, 0.5f, -0.5f}, n5, null);
+ final var v21 = new MeshVertex(new float[] {-0.5f, -0.5f, 0.5f}, n5, null);
+ final var v22 = new MeshVertex(new float[] {0.5f, -0.5f, 0.5f}, n3, null);
+ final var v23 = new MeshVertex(new float[] {0.5f, 0.5f, 0.5f}, n3, null);
+ final var v24 = new MeshVertex(new float[] {-0.5f, 0.5f, 0.5f}, n5, null);
+
+ mesh.addVertex(v1);
+ mesh.addVertex(v2);
+ mesh.addVertex(v3);
+ mesh.addVertex(v4);
+ mesh.addVertex(v5);
+ mesh.addVertex(v6);
+ mesh.addVertex(v7);
+ mesh.addVertex(v8);
+ mesh.addVertex(v9);
+ mesh.addVertex(v10);
+ mesh.addVertex(v11);
+ mesh.addVertex(v12);
+ mesh.addVertex(v13);
+ mesh.addVertex(v14);
+ mesh.addVertex(v15);
+ mesh.addVertex(v16);
+ mesh.addVertex(v17);
+ mesh.addVertex(v18);
+ mesh.addVertex(v19);
+ mesh.addVertex(v20);
+ mesh.addVertex(v21);
+ mesh.addVertex(v22);
+ mesh.addVertex(v23);
+ mesh.addVertex(v24);
+
+ part.addFace(new MeshFace(new int[] {0, 1, 2}));
+ part.addFace(new MeshFace(new int[] {2, 3, 0}));
+ part.addFace(new MeshFace(new int[] {4, 5, 6}));
+ part.addFace(new MeshFace(new int[] {6, 7, 4}));
+ part.addFace(new MeshFace(new int[] {8, 9, 13}));
+ part.addFace(new MeshFace(new int[] {13, 12, 8}));
+ part.addFace(new MeshFace(new int[] {17, 18, 22}));
+ part.addFace(new MeshFace(new int[] {22, 21, 17}));
+ part.addFace(new MeshFace(new int[] {10, 11, 15}));
+ part.addFace(new MeshFace(new int[] {15, 14, 10}));
+ part.addFace(new MeshFace(new int[] {19, 16, 20}));
+ part.addFace(new MeshFace(new int[] {20, 23, 19}));
+
+ part.setMaterial(new ObjMaterial("Default"));
+ mesh.addPart(part);
+ mesh.setBounds(new BoundingBox(new Vec3f(-0.5f, -0.5f, -0.5f), new Vec3f(0.5f, 0.5f, 0.5f)));
+
+ return mesh;
+ }
+
+ private static Mesh generateCapsule()
+ {
+ // TODO
+ return null;
+ }
+
+ private static Mesh generateUnitCylinder()
+ {
+ final Mesh mesh = new Mesh();
+ final MeshPart part = new MeshPart();
+
+ final var nTop = new float[] {0, 0, 1};
+ final var nBottom = new float[] {0, 0, -1};
+ final var topMid = new MeshVertex(new float[] {0, 0, 0.5f}, nTop, null);
+ mesh.addVertex(topMid);
+ final var bottomMid = new MeshVertex(new float[] {0, 0, -0.5f}, nBottom, null);
+ mesh.addVertex(bottomMid);
+
+ final int segments = 50;
+ for (int i = 0; i < segments; i++) {
+ final double angle1 = i * (2 * Math.PI / segments);
+ final double angle2 = (i + 1) * (2 * Math.PI / segments);
+ final float x1 = (float) Math.cos(angle1);
+ final float y1 = (float) Math.sin(angle1);
+ final float x2 = (float) Math.cos(angle2);
+ final float y2 = (float) Math.sin(angle2);
+
+ // Top
+ final var top1 = new MeshVertex(new float[] {x1, y1, 0.5f}, nTop, null);
+ final var top2 = new MeshVertex(new float[] {x2, y2, 0.5f}, nTop, null);
+ mesh.addVertex(top1);
+ mesh.addVertex(top2);
+ part.addFace(new MeshFace(new int[] {0, mesh.getVertices().size() - 1, mesh.getVertices().size() - 2}));
+
+ // Bottom
+ final var bottom1 = new MeshVertex(new float[] {x1, y1, -0.5f}, nBottom, null);
+ final var bottom2 = new MeshVertex(new float[] {x2, y2, -0.5f}, nBottom, null);
+ mesh.addVertex(bottom1);
+ mesh.addVertex(bottom2);
+ part.addFace(new MeshFace(new int[] {1, mesh.getVertices().size() - 1, mesh.getVertices().size() - 2}));
+
+ // Side
+ final var side1 = new MeshVertex(new float[] {x1, y1, 0.5f}, new float[] {x1, y1, 0}, null);
+ final var side2 = new MeshVertex(new float[] {x2, y2, 0.5f}, new float[] {x2, y2, 0}, null);
+ final var side3 = new MeshVertex(new float[] {x1, y1, -0.5f}, new float[] {x1, y1, 0}, null);
+ final var side4 = new MeshVertex(new float[] {x2, y2, -0.5f}, new float[] {x2, y2, 0}, null);
+ mesh.addVertex(side1);
+ mesh.addVertex(side2);
+ mesh.addVertex(side3);
+ mesh.addVertex(side4);
+ part.addFace(new MeshFace(new int[] {
+ mesh.getVertices().size() - 4, mesh.getVertices().size() - 3, mesh.getVertices().size() - 2}));
+ part.addFace(new MeshFace(new int[] {
+ mesh.getVertices().size() - 3, mesh.getVertices().size() - 1, mesh.getVertices().size() - 2}));
+ }
+
+ part.setMaterial(new ObjMaterial("Default"));
+ mesh.addPart(part);
+ mesh.setBounds(new BoundingBox(new Vec3f(-1, -1, -0.5f), new Vec3f(1, 1, 0.5f)));
+
+ return mesh;
+ }
+}
diff --git a/jsgl/src/main/java/jsgl/math/PerlinNoise.java b/jsgl/src/main/java/jsgl/math/PerlinNoise.java
new file mode 100644
index 00000000..14bc7fe7
--- /dev/null
+++ b/jsgl/src/main/java/jsgl/math/PerlinNoise.java
@@ -0,0 +1,1352 @@
+/*
+ * The code of this class is licensed under the MIT license:
+ *
+ * Copyright (c) 2022 Overrun Organization
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package jsgl.math;
+
+/**
+ *
{@code float stb_perlin_noise3( float x,
+ * float y,
+ * float z,
+ * int x_wrap=0,
+ * int y_wrap=0,
+ * int z_wrap=0)}
+ *
+ * This function computes a random value at the coordinate (x,y,z).
+ * Adjacent random values are continuous but the noise fluctuates
+ * its randomness with period 1, i.e. takes on wholly unrelated values
+ * at integer points. Specifically, this implements Ken Perlin's
+ * revised noise function from 2002.
+ *
+ * The "wrap" parameters can be used to create wraparound noise that
+ * wraps at powers of two. The numbers MUST be powers of two. Specify
+ * 0 to mean "don't care". (The noise always wraps every 256 due
+ * details of the implementation, even if you ask for larger or no
+ * wrapping.)
+ *
+ *
{@code float stb_perlin_noise3_seed( float x,
+ * float y,
+ * float z,
+ * int x_wrap=0,
+ * int y_wrap=0,
+ * int z_wrap=0,
+ * int seed)}
+ *
+ * As above, but 'seed' selects from multiple different variations of the
+ * noise function. The current implementation only uses the bottom 8 bits
+ * of 'seed', but possibly in the future more bits will be used.
+ *
+ *
+ * Fractal Noise:
+ *
+ * Three common fractal noise functions are included, which produce
+ * a wide variety of nice effects depending on the parameters
+ * provided. Note that each function will call stb_perlin_noise3
+ * 'octaves' times, so this parameter will affect runtime.
+ *
+ *
{@code float stb_perlin_ridge_noise3(float x, float y, float z,
+ * float lacunarity, float gain, float offset, int octaves)
+ *
+ * float stb_perlin_fbm_noise3(float x, float y, float z,
+ * float lacunarity, float gain, int octaves)
+ *
+ * float stb_perlin_turbulence_noise3(float x, float y, float z,
+ * float lacunarity, float gain, int octaves)}
+ *
+ * Typical values to start playing with:
+ *
+ * - octaves = 6 -- number of "octaves" of noise3() to sum
+ * - lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
+ * - gain = 0.5 -- relative weighting applied to each successive octave
+ * - offset = 1.0? -- used to invert the ridges, may need to be larger, not sure
+ *
+ *
+ * @author squid233
+ * @since 0.1.0
+ */
+public final class PerlinNoise
+{
+ /**
+ * @author squid233
+ * @since 0.1.0
+ */
+ private static final class Vector3b
+ {
+ public final byte x, y, z;
+
+ public Vector3b(int x, int y, int z)
+ {
+ this.x = (byte) x;
+ this.y = (byte) y;
+ this.z = (byte) z;
+ }
+ }
+
+ private static final byte[] RANDTAB = {
+ 23,
+ 125,
+ -95,
+ 52,
+ 103,
+ 117,
+ 70,
+ 37,
+ -9,
+ 101,
+ -53,
+ -87,
+ 124,
+ 126,
+ 44,
+ 123,
+ -104,
+ -18,
+ -111,
+ 45,
+ -85,
+ 114,
+ -3,
+ 10,
+ -64,
+ -120,
+ 4,
+ -99,
+ -7,
+ 30,
+ 35,
+ 72,
+ -81,
+ 63,
+ 77,
+ 90,
+ -75,
+ 16,
+ 96,
+ 111,
+ -123,
+ 104,
+ 75,
+ -94,
+ 93,
+ 56,
+ 66,
+ -16,
+ 8,
+ 50,
+ 84,
+ -27,
+ 49,
+ -46,
+ -83,
+ -17,
+ -115,
+ 1,
+ 87,
+ 18,
+ 2,
+ -58,
+ -113,
+ 57,
+ -31,
+ -96,
+ 58,
+ -39,
+ -88,
+ -50,
+ -11,
+ -52,
+ -57,
+ 6,
+ 73,
+ 60,
+ 20,
+ -26,
+ -45,
+ -23,
+ 94,
+ -56,
+ 88,
+ 9,
+ 74,
+ -101,
+ 33,
+ 15,
+ -37,
+ -126,
+ -30,
+ -54,
+ 83,
+ -20,
+ 42,
+ -84,
+ -91,
+ -38,
+ 55,
+ -34,
+ 46,
+ 107,
+ 98,
+ -102,
+ 109,
+ 67,
+ -60,
+ -78,
+ 127,
+ -98,
+ 13,
+ -13,
+ 65,
+ 79,
+ -90,
+ -8,
+ 25,
+ -32,
+ 115,
+ 80,
+ 68,
+ 51,
+ -72,
+ -128,
+ -24,
+ -48,
+ -105,
+ 122,
+ 26,
+ -44,
+ 105,
+ 43,
+ -77,
+ -43,
+ -21,
+ -108,
+ -110,
+ 89,
+ 14,
+ -61,
+ 28,
+ 78,
+ 112,
+ 76,
+ -6,
+ 47,
+ 24,
+ -5,
+ -116,
+ 108,
+ -70,
+ -66,
+ -28,
+ -86,
+ -73,
+ -117,
+ 39,
+ -68,
+ -12,
+ -10,
+ -124,
+ 48,
+ 119,
+ -112,
+ -76,
+ -118,
+ -122,
+ -63,
+ 82,
+ -74,
+ 120,
+ 121,
+ 86,
+ -36,
+ -47,
+ 3,
+ 91,
+ -15,
+ -107,
+ 85,
+ -51,
+ -106,
+ 113,
+ -40,
+ 31,
+ 100,
+ 41,
+ -92,
+ -79,
+ -42,
+ -103,
+ -25,
+ 38,
+ 71,
+ -71,
+ -82,
+ 97,
+ -55,
+ 29,
+ 95,
+ 7,
+ 92,
+ 54,
+ -2,
+ -65,
+ 118,
+ 34,
+ -35,
+ -125,
+ 11,
+ -93,
+ 99,
+ -22,
+ 81,
+ -29,
+ -109,
+ -100,
+ -80,
+ 17,
+ -114,
+ 69,
+ 12,
+ 110,
+ 62,
+ 27,
+ -1,
+ 0,
+ -62,
+ 59,
+ 116,
+ -14,
+ -4,
+ 19,
+ 21,
+ -69,
+ 53,
+ -49,
+ -127,
+ 64,
+ -121,
+ 61,
+ 40,
+ -89,
+ -19,
+ 102,
+ -33,
+ 106,
+ -97,
+ -59,
+ -67,
+ -41,
+ -119,
+ 36,
+ 32,
+ 22,
+ 5,
+
+ // and a second copy, so we don't need an extra mask or static initializer
+ 23,
+ 125,
+ -95,
+ 52,
+ 103,
+ 117,
+ 70,
+ 37,
+ -9,
+ 101,
+ -53,
+ -87,
+ 124,
+ 126,
+ 44,
+ 123,
+ -104,
+ -18,
+ -111,
+ 45,
+ -85,
+ 114,
+ -3,
+ 10,
+ -64,
+ -120,
+ 4,
+ -99,
+ -7,
+ 30,
+ 35,
+ 72,
+ -81,
+ 63,
+ 77,
+ 90,
+ -75,
+ 16,
+ 96,
+ 111,
+ -123,
+ 104,
+ 75,
+ -94,
+ 93,
+ 56,
+ 66,
+ -16,
+ 8,
+ 50,
+ 84,
+ -27,
+ 49,
+ -46,
+ -83,
+ -17,
+ -115,
+ 1,
+ 87,
+ 18,
+ 2,
+ -58,
+ -113,
+ 57,
+ -31,
+ -96,
+ 58,
+ -39,
+ -88,
+ -50,
+ -11,
+ -52,
+ -57,
+ 6,
+ 73,
+ 60,
+ 20,
+ -26,
+ -45,
+ -23,
+ 94,
+ -56,
+ 88,
+ 9,
+ 74,
+ -101,
+ 33,
+ 15,
+ -37,
+ -126,
+ -30,
+ -54,
+ 83,
+ -20,
+ 42,
+ -84,
+ -91,
+ -38,
+ 55,
+ -34,
+ 46,
+ 107,
+ 98,
+ -102,
+ 109,
+ 67,
+ -60,
+ -78,
+ 127,
+ -98,
+ 13,
+ -13,
+ 65,
+ 79,
+ -90,
+ -8,
+ 25,
+ -32,
+ 115,
+ 80,
+ 68,
+ 51,
+ -72,
+ -128,
+ -24,
+ -48,
+ -105,
+ 122,
+ 26,
+ -44,
+ 105,
+ 43,
+ -77,
+ -43,
+ -21,
+ -108,
+ -110,
+ 89,
+ 14,
+ -61,
+ 28,
+ 78,
+ 112,
+ 76,
+ -6,
+ 47,
+ 24,
+ -5,
+ -116,
+ 108,
+ -70,
+ -66,
+ -28,
+ -86,
+ -73,
+ -117,
+ 39,
+ -68,
+ -12,
+ -10,
+ -124,
+ 48,
+ 119,
+ -112,
+ -76,
+ -118,
+ -122,
+ -63,
+ 82,
+ -74,
+ 120,
+ 121,
+ 86,
+ -36,
+ -47,
+ 3,
+ 91,
+ -15,
+ -107,
+ 85,
+ -51,
+ -106,
+ 113,
+ -40,
+ 31,
+ 100,
+ 41,
+ -92,
+ -79,
+ -42,
+ -103,
+ -25,
+ 38,
+ 71,
+ -71,
+ -82,
+ 97,
+ -55,
+ 29,
+ 95,
+ 7,
+ 92,
+ 54,
+ -2,
+ -65,
+ 118,
+ 34,
+ -35,
+ -125,
+ 11,
+ -93,
+ 99,
+ -22,
+ 81,
+ -29,
+ -109,
+ -100,
+ -80,
+ 17,
+ -114,
+ 69,
+ 12,
+ 110,
+ 62,
+ 27,
+ -1,
+ 0,
+ -62,
+ 59,
+ 116,
+ -14,
+ -4,
+ 19,
+ 21,
+ -69,
+ 53,
+ -49,
+ -127,
+ 64,
+ -121,
+ 61,
+ 40,
+ -89,
+ -19,
+ 102,
+ -33,
+ 106,
+ -97,
+ -59,
+ -67,
+ -41,
+ -119,
+ 36,
+ 32,
+ 22,
+ 5,
+ };
+
+ // perlin's gradient has 12 cases so some get used 1/16th of the time
+ // and some 2/16ths. We reduce bias by changing those fractions
+ // to 5/64ths and 6/64ths
+
+ private static final byte[] RANDTAB_GRAD_IDX = {
+ 7,
+ 9,
+ 5,
+ 0,
+ 11,
+ 1,
+ 6,
+ 9,
+ 3,
+ 9,
+ 11,
+ 1,
+ 8,
+ 10,
+ 4,
+ 7,
+ 8,
+ 6,
+ 1,
+ 5,
+ 3,
+ 10,
+ 9,
+ 10,
+ 0,
+ 8,
+ 4,
+ 1,
+ 5,
+ 2,
+ 7,
+ 8,
+ 7,
+ 11,
+ 9,
+ 10,
+ 1,
+ 0,
+ 4,
+ 7,
+ 5,
+ 0,
+ 11,
+ 6,
+ 1,
+ 4,
+ 2,
+ 8,
+ 8,
+ 10,
+ 4,
+ 9,
+ 9,
+ 2,
+ 5,
+ 7,
+ 9,
+ 1,
+ 7,
+ 2,
+ 2,
+ 6,
+ 11,
+ 5,
+ 5,
+ 4,
+ 6,
+ 9,
+ 0,
+ 1,
+ 1,
+ 0,
+ 7,
+ 6,
+ 9,
+ 8,
+ 4,
+ 10,
+ 3,
+ 1,
+ 2,
+ 8,
+ 8,
+ 9,
+ 10,
+ 11,
+ 5,
+ 11,
+ 11,
+ 2,
+ 6,
+ 10,
+ 3,
+ 4,
+ 2,
+ 4,
+ 9,
+ 10,
+ 3,
+ 2,
+ 6,
+ 3,
+ 6,
+ 10,
+ 5,
+ 3,
+ 4,
+ 10,
+ 11,
+ 2,
+ 9,
+ 11,
+ 1,
+ 11,
+ 10,
+ 4,
+ 9,
+ 4,
+ 11,
+ 0,
+ 4,
+ 11,
+ 4,
+ 0,
+ 0,
+ 0,
+ 7,
+ 6,
+ 10,
+ 4,
+ 1,
+ 3,
+ 11,
+ 5,
+ 3,
+ 4,
+ 2,
+ 9,
+ 1,
+ 3,
+ 0,
+ 1,
+ 8,
+ 0,
+ 6,
+ 7,
+ 8,
+ 7,
+ 0,
+ 4,
+ 6,
+ 10,
+ 8,
+ 2,
+ 3,
+ 11,
+ 11,
+ 8,
+ 0,
+ 2,
+ 4,
+ 8,
+ 3,
+ 0,
+ 0,
+ 10,
+ 6,
+ 1,
+ 2,
+ 2,
+ 4,
+ 5,
+ 6,
+ 0,
+ 1,
+ 3,
+ 11,
+ 9,
+ 5,
+ 5,
+ 9,
+ 6,
+ 9,
+ 8,
+ 3,
+ 8,
+ 1,
+ 8,
+ 9,
+ 6,
+ 9,
+ 11,
+ 10,
+ 7,
+ 5,
+ 6,
+ 5,
+ 9,
+ 1,
+ 3,
+ 7,
+ 0,
+ 2,
+ 10,
+ 11,
+ 2,
+ 6,
+ 1,
+ 3,
+ 11,
+ 7,
+ 7,
+ 2,
+ 1,
+ 7,
+ 3,
+ 0,
+ 8,
+ 1,
+ 1,
+ 5,
+ 0,
+ 6,
+ 10,
+ 11,
+ 11,
+ 0,
+ 2,
+ 7,
+ 0,
+ 10,
+ 8,
+ 3,
+ 5,
+ 7,
+ 1,
+ 11,
+ 1,
+ 0,
+ 7,
+ 9,
+ 0,
+ 11,
+ 5,
+ 10,
+ 3,
+ 2,
+ 3,
+ 5,
+ 9,
+ 7,
+ 9,
+ 8,
+ 4,
+ 6,
+ 5,
+
+ // and a second copy, so we don't need an extra mask or static initializer
+ 7,
+ 9,
+ 5,
+ 0,
+ 11,
+ 1,
+ 6,
+ 9,
+ 3,
+ 9,
+ 11,
+ 1,
+ 8,
+ 10,
+ 4,
+ 7,
+ 8,
+ 6,
+ 1,
+ 5,
+ 3,
+ 10,
+ 9,
+ 10,
+ 0,
+ 8,
+ 4,
+ 1,
+ 5,
+ 2,
+ 7,
+ 8,
+ 7,
+ 11,
+ 9,
+ 10,
+ 1,
+ 0,
+ 4,
+ 7,
+ 5,
+ 0,
+ 11,
+ 6,
+ 1,
+ 4,
+ 2,
+ 8,
+ 8,
+ 10,
+ 4,
+ 9,
+ 9,
+ 2,
+ 5,
+ 7,
+ 9,
+ 1,
+ 7,
+ 2,
+ 2,
+ 6,
+ 11,
+ 5,
+ 5,
+ 4,
+ 6,
+ 9,
+ 0,
+ 1,
+ 1,
+ 0,
+ 7,
+ 6,
+ 9,
+ 8,
+ 4,
+ 10,
+ 3,
+ 1,
+ 2,
+ 8,
+ 8,
+ 9,
+ 10,
+ 11,
+ 5,
+ 11,
+ 11,
+ 2,
+ 6,
+ 10,
+ 3,
+ 4,
+ 2,
+ 4,
+ 9,
+ 10,
+ 3,
+ 2,
+ 6,
+ 3,
+ 6,
+ 10,
+ 5,
+ 3,
+ 4,
+ 10,
+ 11,
+ 2,
+ 9,
+ 11,
+ 1,
+ 11,
+ 10,
+ 4,
+ 9,
+ 4,
+ 11,
+ 0,
+ 4,
+ 11,
+ 4,
+ 0,
+ 0,
+ 0,
+ 7,
+ 6,
+ 10,
+ 4,
+ 1,
+ 3,
+ 11,
+ 5,
+ 3,
+ 4,
+ 2,
+ 9,
+ 1,
+ 3,
+ 0,
+ 1,
+ 8,
+ 0,
+ 6,
+ 7,
+ 8,
+ 7,
+ 0,
+ 4,
+ 6,
+ 10,
+ 8,
+ 2,
+ 3,
+ 11,
+ 11,
+ 8,
+ 0,
+ 2,
+ 4,
+ 8,
+ 3,
+ 0,
+ 0,
+ 10,
+ 6,
+ 1,
+ 2,
+ 2,
+ 4,
+ 5,
+ 6,
+ 0,
+ 1,
+ 3,
+ 11,
+ 9,
+ 5,
+ 5,
+ 9,
+ 6,
+ 9,
+ 8,
+ 3,
+ 8,
+ 1,
+ 8,
+ 9,
+ 6,
+ 9,
+ 11,
+ 10,
+ 7,
+ 5,
+ 6,
+ 5,
+ 9,
+ 1,
+ 3,
+ 7,
+ 0,
+ 2,
+ 10,
+ 11,
+ 2,
+ 6,
+ 1,
+ 3,
+ 11,
+ 7,
+ 7,
+ 2,
+ 1,
+ 7,
+ 3,
+ 0,
+ 8,
+ 1,
+ 1,
+ 5,
+ 0,
+ 6,
+ 10,
+ 11,
+ 11,
+ 0,
+ 2,
+ 7,
+ 0,
+ 10,
+ 8,
+ 3,
+ 5,
+ 7,
+ 1,
+ 11,
+ 1,
+ 0,
+ 7,
+ 9,
+ 0,
+ 11,
+ 5,
+ 10,
+ 3,
+ 2,
+ 3,
+ 5,
+ 9,
+ 7,
+ 9,
+ 8,
+ 4,
+ 6,
+ 5,
+ };
+ private static final Vector3b[] basis = {
+ new Vector3b(1, 1, 0),
+ new Vector3b(-1, 1, 0),
+ new Vector3b(1, -1, 0),
+ new Vector3b(-1, -1, 0),
+ new Vector3b(1, 0, 1),
+ new Vector3b(-1, 0, 1),
+ new Vector3b(1, 0, -1),
+ new Vector3b(-1, 0, -1),
+ new Vector3b(0, 1, 1),
+ new Vector3b(0, -1, 1),
+ new Vector3b(0, 1, -1),
+ new Vector3b(0, -1, -1),
+ };
+
+ private static float lerp(float a, float b, float t)
+ {
+ return a + (b - a) * t;
+ }
+
+ private static int fastfloor(float a)
+ {
+ int ai = (int) a;
+ return (a < ai) ? ai - 1 : ai;
+ }
+
+ // different grad function from Perlin's, but easy to modify to match reference
+ private static float grad(int grad_idx, float x, float y, float z)
+ {
+ Vector3b grad = basis[grad_idx];
+ return grad.x * x + grad.y * y + grad.z * z;
+ }
+
+ private static float noise3internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, byte seed)
+ {
+ float u, v, w;
+ float n000, n001, n010, n011, n100, n101, n110, n111;
+ float n00, n01, n10, n11;
+ float n0, n1;
+
+ int x_mask = (x_wrap - 1) & 255;
+ int y_mask = (y_wrap - 1) & 255;
+ int z_mask = (z_wrap - 1) & 255;
+ int px = fastfloor(x);
+ int py = fastfloor(y);
+ int pz = fastfloor(z);
+ int x0 = px & x_mask, x1 = (px + 1) & x_mask;
+ int y0 = py & y_mask, y1 = (py + 1) & y_mask;
+ int z0 = pz & z_mask, z1 = (pz + 1) & z_mask;
+ int r0, r1, r00, r01, r10, r11;
+
+ x -= px;
+ u = (((x * 6 - 15) * x + 10) * x * x * x);
+ y -= py;
+ v = (((y * 6 - 15) * y + 10) * y * y * y);
+ z -= pz;
+ w = (((z * 6 - 15) * z + 10) * z * z * z);
+
+ final int seed_i = seed & 0xff;
+ r0 = RANDTAB[x0 + seed_i] & 0xff;
+ r1 = RANDTAB[x1 + seed_i] & 0xff;
+
+ r00 = RANDTAB[r0 + y0] & 0xff;
+ r01 = RANDTAB[r0 + y1] & 0xff;
+ r10 = RANDTAB[r1 + y0] & 0xff;
+ r11 = RANDTAB[r1 + y1] & 0xff;
+
+ n000 = grad(RANDTAB_GRAD_IDX[r00 + z0], x, y, z);
+ n001 = grad(RANDTAB_GRAD_IDX[r00 + z1], x, y, z - 1);
+ n010 = grad(RANDTAB_GRAD_IDX[r01 + z0], x, y - 1, z);
+ n011 = grad(RANDTAB_GRAD_IDX[r01 + z1], x, y - 1, z - 1);
+ n100 = grad(RANDTAB_GRAD_IDX[r10 + z0], x - 1, y, z);
+ n101 = grad(RANDTAB_GRAD_IDX[r10 + z1], x - 1, y, z - 1);
+ n110 = grad(RANDTAB_GRAD_IDX[r11 + z0], x - 1, y - 1, z);
+ n111 = grad(RANDTAB_GRAD_IDX[r11 + z1], x - 1, y - 1, z - 1);
+
+ n00 = lerp(n000, n001, w);
+ n01 = lerp(n010, n011, w);
+ n10 = lerp(n100, n101, w);
+ n11 = lerp(n110, n111, w);
+
+ n0 = lerp(n00, n01, v);
+ n1 = lerp(n10, n11, v);
+
+ return lerp(n0, n1, u);
+ }
+
+ public static float noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap)
+ {
+ return noise3internal(x, y, z, x_wrap, y_wrap, z_wrap, (byte) 0);
+ }
+
+ public static float noise3seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed)
+ {
+ return noise3internal(x, y, z, x_wrap, y_wrap, z_wrap, (byte) seed);
+ }
+
+ public static float ridgeNoise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves)
+ {
+ float frequency = 1.0f;
+ float prev = 1.0f;
+ float amplitude = 0.5f;
+ float sum = 0.0f;
+
+ for (int i = 0; i < octaves; i++) {
+ float r = noise3internal(x * frequency, y * frequency, z * frequency, 0, 0, 0, (byte) i);
+ r = offset - Math.abs(r);
+ r = r * r;
+ sum += r * amplitude * prev;
+ prev = r;
+ frequency *= lacunarity;
+ amplitude *= gain;
+ }
+ return sum;
+ }
+
+ public static float fbmNoise3(float x, float y, float z, float lacunarity, float gain, int octaves)
+ {
+ float frequency = 1.0f;
+ float amplitude = 1.0f;
+ float sum = 0.0f;
+
+ for (int i = 0; i < octaves; i++) {
+ sum += noise3internal(x * frequency, y * frequency, z * frequency, 0, 0, 0, (byte) i) * amplitude;
+ frequency *= lacunarity;
+ amplitude *= gain;
+ }
+ return sum;
+ }
+
+ public static float turbulenceNoise3(float x, float y, float z, float lacunarity, float gain, int octaves)
+ {
+ int i;
+ float frequency = 1.0f;
+ float amplitude = 1.0f;
+ float sum = 0.0f;
+
+ for (i = 0; i < octaves; i++) {
+ float r = noise3internal(x * frequency, y * frequency, z * frequency, 0, 0, 0, (byte) i) * amplitude;
+ sum += Math.abs(r);
+ frequency *= lacunarity;
+ amplitude *= gain;
+ }
+ return sum;
+ }
+
+ public static float noise3wrapNonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, byte seed)
+ {
+ float u, v, w;
+ float n000, n001, n010, n011, n100, n101, n110, n111;
+ float n00, n01, n10, n11;
+ float n0, n1;
+
+ int px = fastfloor(x);
+ int py = fastfloor(y);
+ int pz = fastfloor(z);
+ int x_wrap2 = ((x_wrap != 0) ? x_wrap : 256);
+ int y_wrap2 = ((y_wrap != 0) ? y_wrap : 256);
+ int z_wrap2 = ((z_wrap != 0) ? z_wrap : 256);
+ int x0 = px % x_wrap2, x1;
+ int y0 = py % y_wrap2, y1;
+ int z0 = pz % z_wrap2, z1;
+ int r0, r1, r00, r01, r10, r11;
+
+ if (x0 < 0)
+ x0 += x_wrap2;
+ if (y0 < 0)
+ y0 += y_wrap2;
+ if (z0 < 0)
+ z0 += z_wrap2;
+ x1 = (x0 + 1) % x_wrap2;
+ y1 = (y0 + 1) % y_wrap2;
+ z1 = (z0 + 1) % z_wrap2;
+
+ x -= px;
+ u = (((x * 6 - 15) * x + 10) * x * x * x);
+ y -= py;
+ v = (((y * 6 - 15) * y + 10) * y * y * y);
+ z -= pz;
+ w = (((z * 6 - 15) * z + 10) * z * z * z);
+
+ final int seed_i = seed & 0xff;
+ r0 = RANDTAB[x0] & 0xff;
+ r0 = RANDTAB[r0 + seed_i] & 0xff;
+ r1 = RANDTAB[x1] & 0xff;
+ r1 = RANDTAB[r1 + seed_i] & 0xff;
+
+ r00 = RANDTAB[r0 + y0] & 0xff;
+ r01 = RANDTAB[r0 + y1] & 0xff;
+ r10 = RANDTAB[r1 + y0] & 0xff;
+ r11 = RANDTAB[r1 + y1] & 0xff;
+
+ n000 = grad(RANDTAB_GRAD_IDX[r00 + z0], x, y, z);
+ n001 = grad(RANDTAB_GRAD_IDX[r00 + z1], x, y, z - 1);
+ n010 = grad(RANDTAB_GRAD_IDX[r01 + z0], x, y - 1, z);
+ n011 = grad(RANDTAB_GRAD_IDX[r01 + z1], x, y - 1, z - 1);
+ n100 = grad(RANDTAB_GRAD_IDX[r10 + z0], x - 1, y, z);
+ n101 = grad(RANDTAB_GRAD_IDX[r10 + z1], x - 1, y, z - 1);
+ n110 = grad(RANDTAB_GRAD_IDX[r11 + z0], x - 1, y - 1, z);
+ n111 = grad(RANDTAB_GRAD_IDX[r11 + z1], x - 1, y - 1, z - 1);
+
+ n00 = lerp(n000, n001, w);
+ n01 = lerp(n010, n011, w);
+ n10 = lerp(n100, n101, w);
+ n11 = lerp(n110, n111, w);
+
+ n0 = lerp(n00, n01, v);
+ n1 = lerp(n10, n11, v);
+
+ return lerp(n0, n1, u);
+ }
+}
diff --git a/src/main/java/rv/comm/rcssserver/GameState.java b/src/main/java/rv/comm/rcssserver/GameState.java
index db8ad449..fa806428 100644
--- a/src/main/java/rv/comm/rcssserver/GameState.java
+++ b/src/main/java/rv/comm/rcssserver/GameState.java
@@ -18,12 +18,14 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.magmaoffenburg.roboviz.configuration.Config;
import rv.comm.rcssserver.ServerComm.ServerChangeListener;
import rv.ui.screens.FoulListOverlay;
-import rv.world.WorldModel;
/**
* Contains soccer game state information collected from rcssserver: teams, scores, play mode, time,
@@ -65,7 +67,9 @@ public enum FoulType
KICKOFF(5, "illegal kickoff"),
CHARGING(6, "charging"),
SELF_COLLISION(7, "self collision"),
- BALL_HOLDING(8, "ball holding");
+ BALL_HOLDING(8, "ball holding"),
+ DOUBLE_TOUCH(9, "double touching"),
+ HAND_FOUL(10, "hand foul");
private int index;
private String name;
@@ -104,6 +108,13 @@ public record HistoryItem(float time, String playMode)
public static final String GOAL_DEPTH = "GoalDepth";
public static final String GOAL_HEIGHT = "GoalHeight";
public static final String FREE_KICK_DST = "FreeKickDistance";
+ public static final String CENTER_CIRCLE_RADIUS = "CenterCircleRadius";
+ public static final String CORNER_AREA_RADIUS = "CornerAreaRadius";
+ public static final String PENALTY_SPOT_DISTANCE = "PenaltySpotDistance";
+ public static final String PENALTY_AREA_LENGTH = "PenaltyAreaLength";
+ public static final String PENALTY_AREA_WIDTH = "PenaltyAreaWidth";
+ public static final String GOALIE_AREA_LENGTH = "GoalieAreaLength";
+ public static final String GOALIE_AREA_WIDTH = "GoalieAreaWidth";
public static final String WAIT_BEFORE_KO = "WaitBeforeKickOff";
public static final String AGENT_RADIUS = "AgentRadius";
public static final String BALL_RADIUS = "BallRadius";
@@ -154,6 +165,8 @@ public record HistoryItem(float time, String playMode)
// Foul
public static final String FOUL = "foul";
+ private static final Logger LOGGER = LogManager.getLogger();
+
private boolean initialized;
private float fieldLength;
private float fieldWidth;
@@ -162,6 +175,13 @@ public record HistoryItem(float time, String playMode)
private float goalDepth;
private float goalHeight;
private float freeKickDist;
+ private float centerCircleRadius;
+ private float cornerAreaRadius;
+ private float penaltySpotDistance;
+ private float penaltyAreaLength;
+ private float penaltyAreaWidth;
+ private float goalieAreaLength;
+ private float goalieAreaWidth;
private float waitBeforeKickoff;
private float agentRadius;
private float ballRadius;
@@ -179,7 +199,7 @@ public record HistoryItem(float time, String playMode)
private String teamRight;
private int scoreLeft;
private int scoreRight;
- private String playMode = "";
+ private int playModeIndex = -1;
private boolean playModeJustChanged;
private List playModeHistory = new CopyOnWriteArrayList<>();
private float time;
@@ -190,6 +210,10 @@ public record HistoryItem(float time, String playMode)
private Float penaltyShotStartTime = null;
+ private int measureOrRuleChanges = 0;
+ private int timeChanges = 0;
+ private int playStateChanges = 0;
+
private final List listeners = new CopyOnWriteArrayList<>();
private final List smListeners = new CopyOnWriteArrayList<>();
@@ -234,6 +258,41 @@ public float getFreeKickDistance()
return freeKickDist;
}
+ public float getCenterCircleRadius()
+ {
+ return centerCircleRadius > 0.0f ? centerCircleRadius : freeKickDist;
+ }
+
+ public float getCornerAreaRadius()
+ {
+ return cornerAreaRadius;
+ }
+
+ public float getPenaltySpotDistance()
+ {
+ return penaltySpotDistance;
+ }
+
+ public Optional getPenaltyAreaLength()
+ {
+ return penaltyAreaLength > 0 ? Optional.of(penaltyAreaLength) : Optional.empty();
+ }
+
+ public Optional getPenaltyAreaWidth()
+ {
+ return penaltyAreaWidth > 0 ? Optional.of(penaltyAreaWidth) : Optional.empty();
+ }
+
+ public float getGoalieAreaLength()
+ {
+ return goalieAreaLength;
+ }
+
+ public float getGoalieAreaWidth()
+ {
+ return goalieAreaWidth;
+ }
+
public float getWaitBeforeKickOff()
{
return waitBeforeKickoff;
@@ -325,7 +384,9 @@ public int getScoreRight()
public String getPlayMode()
{
- return playMode;
+ if (playModeIndex < 0 || playModes == null)
+ return null;
+ return playModes[playModeIndex];
}
public boolean hasPlayModeJustChanged()
@@ -390,13 +451,23 @@ public void reset()
teamRight = null;
scoreLeft = 0;
scoreRight = 0;
- playMode = null;
+ playModeIndex = -1;
playModeJustChanged = false;
playModeHistory = new ArrayList<>();
time = 0;
half = 0;
fouls = new CopyOnWriteArrayList<>();
+ // Resetting these values is required for keeping backwards compatibility with SimSpark
+ centerCircleRadius = 0;
+ cornerAreaRadius = 0;
+ penaltySpotDistance = 0;
+ goalieAreaLength = 0;
+ goalieAreaWidth = 0;
+
+ penaltyAreaLength = 0;
+ penaltyAreaWidth = 0;
+
// For support of rcssserver3d versions <= 0.7.2
passModeMinOppBallDist = 1;
passModeDuration = 4;
@@ -407,6 +478,7 @@ public void reset()
private boolean isTimeStopped()
{
+ var playMode = getPlayMode();
return BEFORE_KICK_OFF.equals(playMode) || GAME_OVER.equals(playMode);
}
@@ -448,28 +520,25 @@ private void addFoul(Foul foul)
}
}
+ public void parse(List exp, ProtocolVersion version)
+ {
+ if (!version.supports(1, 0)) {
+ LOGGER.error("unsupported game state version: {}", version);
+ }
+ parse(exp);
+ }
+
/**
* Parses expression and updates state
*/
- public void parse(SExp exp, WorldModel world)
+ public void parse(List exp)
{
- if (exp.getChildren() == null)
+ if (exp == null)
return;
- for (ServerMessageReceivedListener l : smListeners) {
- l.gsServerMessageReceived(this);
- }
-
- int measureOrRuleChanges = 0;
- int timeChanges = 0;
- int playStateChanges = 0;
- String previousPlayMode = playMode;
-
- removeExpiredFouls();
- passModeScoreWaitLeft = 0;
- passModeScoreWaitRight = 0;
+ String previousPlayMode = getPlayMode();
- for (SExp se : exp.getChildren()) {
+ for (SExp se : exp) {
String[] atoms = se.getAtoms();
if (atoms != null) {
@@ -504,6 +573,34 @@ public void parse(SExp exp, WorldModel world)
freeKickDist = Float.parseFloat(atoms[1]);
measureOrRuleChanges++;
break;
+ case CENTER_CIRCLE_RADIUS:
+ centerCircleRadius = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
+ case CORNER_AREA_RADIUS:
+ cornerAreaRadius = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
+ case PENALTY_SPOT_DISTANCE:
+ penaltySpotDistance = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
+ case PENALTY_AREA_LENGTH:
+ penaltyAreaLength = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
+ case PENALTY_AREA_WIDTH:
+ penaltyAreaWidth = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
+ case GOALIE_AREA_LENGTH:
+ goalieAreaLength = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
+ case GOALIE_AREA_WIDTH:
+ goalieAreaWidth = Float.parseFloat(atoms[1]);
+ measureOrRuleChanges++;
+ break;
case WAIT_BEFORE_KO:
waitBeforeKickoff = Float.parseFloat(atoms[1]);
measureOrRuleChanges++;
@@ -556,8 +653,7 @@ public void parse(SExp exp, WorldModel world)
timeChanges++;
break;
case PLAY_MODE:
- int mode = Integer.parseInt(atoms[1]);
- playMode = playModes[mode];
+ playModeIndex = Integer.parseInt(atoms[1]);
playStateChanges++;
break;
case TEAM_LEFT:
@@ -579,10 +675,16 @@ public void parse(SExp exp, WorldModel world)
case FOUL:
Foul foul = new Foul();
foul.time = time;
- foul.index = Integer.parseInt(atoms[1]);
- foul.type = GameState.FoulType.values()[Integer.parseInt(atoms[2])];
- foul.team = Integer.parseInt(atoms[3]);
- foul.agentID = Integer.parseInt(atoms[4]);
+ if (atoms.length == 5) {
+ foul.index = Integer.parseInt(atoms[1]);
+ foul.type = GameState.FoulType.values()[Integer.parseInt(atoms[2])];
+ foul.team = Integer.parseInt(atoms[3]);
+ foul.agentID = Integer.parseInt(atoms[4]);
+ } else {
+ foul.type = GameState.FoulType.values()[Integer.parseInt(atoms[1])];
+ foul.team = Integer.parseInt(atoms[2]);
+ foul.agentID = Integer.parseInt(atoms[3]);
+ }
foul.receivedTime = System.currentTimeMillis();
addFoul(foul);
break;
@@ -600,14 +702,14 @@ public void parse(SExp exp, WorldModel world)
}
}
- playModeJustChanged = previousPlayMode == null || !previousPlayMode.equals(playMode);
+ playModeJustChanged = !Objects.equals(previousPlayMode, getPlayMode());
if (playModeJustChanged) {
- playModeHistory.add(new HistoryItem(time, playMode));
+ playModeHistory.add(new HistoryItem(time, getPlayMode()));
while (playModeHistory.size() > 2) {
playModeHistory.remove(0);
}
- switch (playMode) {
+ switch (getPlayMode()) {
case KICK_OFF_LEFT:
case KICK_OFF_RIGHT:
penaltyShotStartTime = time;
@@ -619,7 +721,24 @@ public void parse(SExp exp, WorldModel world)
break;
}
}
+ }
+
+ public void startParseStep()
+ {
+ for (ServerMessageReceivedListener l : smListeners)
+ l.gsServerMessageReceived(this);
+ removeExpiredFouls();
+ passModeScoreWaitLeft = 0;
+ passModeScoreWaitRight = 0;
+
+ measureOrRuleChanges = 0;
+ timeChanges = 0;
+ playStateChanges = 0;
+ }
+
+ public void finishParseStep()
+ {
initialized = true;
for (ServerMessageReceivedListener l : smListeners)
diff --git a/src/main/java/rv/comm/rcssserver/MessageParser.java b/src/main/java/rv/comm/rcssserver/MessageParser.java
index 59d5eb2d..594beb36 100644
--- a/src/main/java/rv/comm/rcssserver/MessageParser.java
+++ b/src/main/java/rv/comm/rcssserver/MessageParser.java
@@ -18,6 +18,10 @@
import java.text.ParseException;
import java.util.ArrayList;
+import java.util.Arrays;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import rv.comm.rcssserver.scenegraph.RSMPSceneGraphType;
import rv.comm.rcssserver.scenegraph.SceneGraph;
import rv.comm.rcssserver.scenegraph.SceneGraphHeader;
import rv.world.WorldModel;
@@ -32,6 +36,8 @@ public class MessageParser
{
private WorldModel world;
+ private static final Logger LOGGER = LogManager.getLogger();
+
public MessageParser(WorldModel world)
{
this.world = world;
@@ -45,18 +51,75 @@ public void setWorldModel(WorldModel world)
public void parse(String message) throws ParseException
{
synchronized (world) {
+ world.getGameState().startParseStep();
+
ArrayList expressions = SExp.parse(message);
- world.getGameState().parse(expressions.get(0), world);
- SceneGraphHeader header = SceneGraphHeader.parse(expressions.get(1));
- if (header.getType().equals(SceneGraphHeader.FULL)) {
- // scene graph structure has changed, so replace the old one and tell
- // any objects that rely on the scene graph to update their references
- SceneGraph sg = new SceneGraph(expressions.get(2));
- world.setSceneGraph(sg);
+ // Check if legacy mode shall be used
+ boolean legacyProtocol = !expressions.get(0).getChildren().get(0).getAtoms()[0].equals("RSMP");
+
+ if (legacyProtocol) {
+ world.getGameState().parse(expressions.get(0).getChildren());
+ SceneGraphHeader header = SceneGraphHeader.parse(expressions.get(1));
+ if (header.getType().equals(SceneGraphHeader.FULL)) {
+ // scene graph structure has changed, so replace the old one and tell
+ // any objects that rely on the scene graph to update their references
+ SceneGraph sg = new SceneGraph(expressions.get(2).getChildren(), false);
+ world.setSceneGraph(sg);
+ } else {
+ world.getSceneGraph().update(expressions.get(2).getChildren());
+ }
} else {
- world.getSceneGraph().update(expressions.get(2));
+ final var root = expressions.get(0).getChildren();
+
+ // RSMP version check
+ var rsmpHeaderAtoms = root.get(0).getAtoms();
+ var rsmpVersion = new ProtocolVersion(Arrays.asList(rsmpHeaderAtoms).subList(1, 3));
+ if (!rsmpVersion.supports(1, 0)) {
+ LOGGER.error("unsupported RSMP version: {}.{}", rsmpHeaderAtoms[1], rsmpHeaderAtoms[2]);
+ return;
+ }
+
+ for (var protocolComponent : root.subList(1, root.size())) {
+ final var header = protocolComponent.getChildren().get(0);
+ final var componentName = header.getAtoms()[0];
+ final var version = new ProtocolVersion(Arrays.asList(header.getAtoms()).subList(1, 3));
+ final var componentContent =
+ protocolComponent.getChildren().subList(1, protocolComponent.getChildren().size());
+ switch (componentName) {
+ case "gt":
+ world.updateGlobalTime(protocolComponent.getAtoms(), version);
+ break;
+ case "sg":
+ if (!version.supports(1, 0)) {
+ LOGGER.error("unsupported scene graph version: {}", version);
+ continue;
+ }
+ final var sceneGraphType = protocolComponent.getAtoms()[0];
+ switch (sceneGraphType) {
+ case RSMPSceneGraphType.FULL:
+ final var sg = new SceneGraph(componentContent, true);
+ world.setSceneGraph(sg);
+ break;
+ case RSMPSceneGraphType.DIFF:
+ if (world.getSceneGraph() != null)
+ world.getSceneGraph().update(componentContent);
+ break;
+ default:
+ LOGGER.error("unsupported scene graph type: {}", protocolComponent.getAtoms()[0]);
+ continue;
+ }
+ break;
+ case "ge":
+ world.getGameState().parse(componentContent, version);
+ break;
+ case "gs":
+ world.getGameState().parse(componentContent, version);
+ break;
+ }
+ }
}
+ world.getGameState().finishParseStep();
}
}
}
diff --git a/src/main/java/rv/comm/rcssserver/ProtocolVersion.java b/src/main/java/rv/comm/rcssserver/ProtocolVersion.java
new file mode 100644
index 00000000..8527db8a
--- /dev/null
+++ b/src/main/java/rv/comm/rcssserver/ProtocolVersion.java
@@ -0,0 +1,22 @@
+package rv.comm.rcssserver;
+
+import java.util.List;
+
+public record ProtocolVersion(int major, int minor)
+{
+ public ProtocolVersion(List version)
+ {
+ this(Integer.parseInt(version.get(0)), Integer.parseInt(version.get(1)));
+ }
+
+ public boolean supports(int major, int minor)
+ {
+ return major == this.major && minor >= this.minor;
+ }
+
+ @Override
+ public String toString()
+ {
+ return major + "." + minor;
+ }
+}
diff --git a/src/main/java/rv/comm/rcssserver/ServerSpeedBenchmarker.java b/src/main/java/rv/comm/rcssserver/ServerSpeedBenchmarker.java
index 92f6468e..2a519253 100644
--- a/src/main/java/rv/comm/rcssserver/ServerSpeedBenchmarker.java
+++ b/src/main/java/rv/comm/rcssserver/ServerSpeedBenchmarker.java
@@ -20,13 +20,14 @@
import java.util.TreeMap;
import rv.comm.rcssserver.GameState.ServerMessageReceivedListener;
import rv.comm.rcssserver.ServerComm.ServerChangeListener;
+import rv.world.WorldModel.GlobalTimeListener;
/**
* Estimates the speed of the server
*
* @author Patrick MacAlpine
*/
-public class ServerSpeedBenchmarker implements ServerMessageReceivedListener, ServerChangeListener
+public class ServerSpeedBenchmarker implements GlobalTimeListener, ServerMessageReceivedListener, ServerChangeListener
{
private static final boolean USE_NANOS = false;
private long msgTime;
@@ -36,6 +37,10 @@ public class ServerSpeedBenchmarker implements ServerMessageReceivedListener, Se
private TreeMap serverMsgDeltas = new TreeMap<>();
private float accumulatedServerTime;
+ private float globalTime = 0.0f;
+ private float prevGlobalTime = 0.0f;
+ private boolean haveGlobalTime = false;
+
public String getServerSpeed()
{
if (serverSpeed < 0) {
@@ -55,8 +60,6 @@ private void updateServerSpeed(GameState gs)
}
final float DEFAULT_MSG_TIME_DELTA = 0.04f;
- float time = gs.getTime();
-
// Add message time info to map
if (serverMsgDeltas.isEmpty()) {
serverMsgDeltas.put(msgTime, -1.0f);
@@ -65,9 +68,11 @@ private void updateServerSpeed(GameState gs)
long lastMsgTime = serverMsgDeltas.lastKey();
float serverTimeDelta;
- if (time - lastGameTime > 0) {
+ if (haveGlobalTime) {
+ serverTimeDelta = globalTime - prevGlobalTime;
+ } else if (gs != null && gs.getTime() - lastGameTime > 0) {
// We have a game time change for the amount of time passed
- serverTimeDelta = time - lastGameTime;
+ serverTimeDelta = gs.getTime() - lastGameTime;
} else {
// The game is paused so use DEFAULT_MSG_TIME_DELTA for amount of time passed
serverTimeDelta = DEFAULT_MSG_TIME_DELTA;
@@ -126,7 +131,8 @@ public void gsServerMessageReceived(GameState gs)
@Override
public void gsServerMessageProcessed(GameState gs)
{
- updateServerSpeed(gs);
+ if (!haveGlobalTime)
+ updateServerSpeed(gs);
}
@Override
@@ -136,4 +142,13 @@ public void connectionChanged(ServerComm server)
serverMsgDeltas.clear();
}
}
+
+ @Override
+ public void globalTimeChanged(float newGlobalTime)
+ {
+ haveGlobalTime = true;
+ prevGlobalTime = globalTime;
+ globalTime = newGlobalTime;
+ updateServerSpeed(null);
+ }
}
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/DescriptionNode.java b/src/main/java/rv/comm/rcssserver/scenegraph/DescriptionNode.java
new file mode 100644
index 00000000..c4852a52
--- /dev/null
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/DescriptionNode.java
@@ -0,0 +1,31 @@
+package rv.comm.rcssserver.scenegraph;
+
+import java.util.ArrayList;
+import rv.comm.rcssserver.SExp;
+
+/**
+ * Contains metadata/descriptions about its children.
+ *
+ * @author Hannes Braun
+ */
+public class DescriptionNode extends Node
+{
+ /** Abbreviation declaring this node type in an s-expression */
+ public static final String EXP_ABRV = "DSC";
+
+ private final ArrayList descriptions;
+
+ public DescriptionNode(Node parent, SExp exp)
+ {
+ super(parent);
+ descriptions = new ArrayList<>(exp.getChildren().size());
+ for (var child : exp.getChildren()) {
+ descriptions.add(child.getAtoms());
+ }
+ }
+
+ public ArrayList getDescriptions()
+ {
+ return descriptions;
+ }
+}
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/GeometryNode.java b/src/main/java/rv/comm/rcssserver/scenegraph/GeometryNode.java
index 7f4b07f5..0b62b9c1 100644
--- a/src/main/java/rv/comm/rcssserver/scenegraph/GeometryNode.java
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/GeometryNode.java
@@ -16,6 +16,7 @@
package rv.comm.rcssserver.scenegraph;
+import java.util.List;
import jsgl.math.vector.Matrix;
import jsgl.math.vector.Vec3f;
import rv.comm.rcssserver.SExp;
@@ -32,7 +33,8 @@ public abstract class GeometryNode extends Node
protected boolean visible = false;
protected Matrix scale = Matrix.createIdentity();
protected String name;
- protected String[] materials;
+ protected String[] materials = new String[0];
+ protected float[] rgba;
public boolean isVisible()
{
@@ -77,19 +79,35 @@ public String[] getMaterials()
return materials;
}
- public GeometryNode(Node parent, SExp exp)
+ public float[] getRGBA()
+ {
+ return rgba;
+ }
+
+ public GeometryNode(Node parent, List exp)
{
super(parent);
applyOperations(exp);
}
- private void applyOperations(SExp exp)
+ private void applyOperations(List exp)
{
- for (SExp e : exp.getChildren()) {
+ boolean ballHack = false;
+ for (SExp e : exp) {
String operation = e.getAtoms()[0];
switch (operation) {
case "load":
load(e);
+ if (parent != null && parent.parent != null && parent.parent instanceof DescriptionNode dn &&
+ dn.getDescriptions().get(0)[0].equals("ball")) {
+ // TODO terrible hack, use the StdUnitSphere instead and use a suitable ball texture.
+ name = "models/soccerball.obj";
+ materials = new String[0];
+ visible = true;
+ transparent = false;
+ rgba = null;
+ ballHack = true;
+ }
break;
case "sSc":
setScale(e);
@@ -98,12 +116,29 @@ private void applyOperations(SExp exp)
visible = e.getAtoms()[1].equals("1");
break;
case "resetMaterials":
+ if (ballHack)
+ break;
materials = new String[e.getAtoms().length - 1];
System.arraycopy(e.getAtoms(), 1, materials, 0, materials.length);
break;
case "sMat":
+ if (ballHack)
+ break;
materials = new String[1];
materials[0] = e.getAtoms()[1];
+ if (materials[0].equals("jersey_mat")) {
+ // TODO this is a hack to hide jersey boxes
+ // I cannot use the suppressed meshes for this because they match on the model name, not the
+ // material name.
+ visible = false;
+ }
+ break;
+ case "rgba":
+ if (ballHack)
+ break;
+ var atoms = e.getAtoms();
+ rgba = new float[] {Float.parseFloat(atoms[1]), Float.parseFloat(atoms[2]), Float.parseFloat(atoms[3]),
+ Float.parseFloat(atoms[4])};
break;
case "setTransparent":
transparent = true;
@@ -120,10 +155,7 @@ private void setScale(SExp exp)
for (int i = 0; i < 3; i++)
xyz[i] = Float.parseFloat(exp.getAtoms()[i + 1]);
scale = Matrix.createScale(new Vec3f(xyz));
- if (localTransform != null)
- localTransform = localTransform.times(scale);
- else
- localTransform = scale;
+ localTransform = scale;
}
public boolean containsMaterial(String name)
@@ -135,9 +167,9 @@ public boolean containsMaterial(String name)
}
@Override
- public void update(SExp exp)
+ public void update(List exp)
{
- if (exp.getChildren() != null) {
+ if (exp != null) {
applyOperations(exp);
}
super.update(exp);
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/Node.java b/src/main/java/rv/comm/rcssserver/scenegraph/Node.java
index a8f25827..6914f718 100644
--- a/src/main/java/rv/comm/rcssserver/scenegraph/Node.java
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/Node.java
@@ -17,6 +17,7 @@
package rv.comm.rcssserver.scenegraph;
import java.util.ArrayList;
+import java.util.List;
import jsgl.math.vector.Matrix;
import rv.comm.rcssserver.SExp;
@@ -86,20 +87,38 @@ public Node(Node parent)
this.parent = parent;
}
- protected void update(SExp exp)
+ protected void update(List exp)
{
- if (exp.getChildren() == null || children == null)
+ if (exp == null || children == null)
return;
// updates in expression should follow same structure as the
// original scene graph, so children are traversed in same order
int childIndex = 0;
int size = children.size();
- for (SExp e : exp.getChildren()) {
+ for (SExp e : exp) {
if (e.getAtoms()[0].equals(Node.DECL_ABRV) && childIndex < size) {
Node child = children.get(childIndex++);
- child.update(e);
+ child.update(e.getChildren());
}
}
}
+
+ /**
+ * Finds a child node with the supplied type
+ * @param recursive whether to recursively search for the node
+ */
+ public T findNodeWithType(Class nodeClass, boolean recursive)
+ {
+ for (var child : children) {
+ if (nodeClass.isInstance(child))
+ return nodeClass.cast(child);
+ else if (recursive) {
+ var result = child.findNodeWithType(nodeClass, recursive);
+ if (result != null)
+ return result;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/RSMPSceneGraphType.java b/src/main/java/rv/comm/rcssserver/scenegraph/RSMPSceneGraphType.java
new file mode 100644
index 00000000..d4c7c087
--- /dev/null
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/RSMPSceneGraphType.java
@@ -0,0 +1,7 @@
+package rv.comm.rcssserver.scenegraph;
+
+public interface RSMPSceneGraphType
+{
+ String FULL = "full";
+ String DIFF = "diff";
+}
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/SceneGraph.java b/src/main/java/rv/comm/rcssserver/scenegraph/SceneGraph.java
index 7a84b072..c0895290 100644
--- a/src/main/java/rv/comm/rcssserver/scenegraph/SceneGraph.java
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/SceneGraph.java
@@ -18,6 +18,8 @@
import java.util.ArrayList;
import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import rv.comm.rcssserver.SExp;
/**
@@ -34,8 +36,17 @@ public interface SceneGraphListener
void updatedSceneGraph(SceneGraph sg);
}
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private final boolean generatedFromRSMP;
+
private final Node root;
+ public boolean isGeneratedFromRSMP()
+ {
+ return generatedFromRSMP;
+ }
+
public Node getRoot()
{
return root;
@@ -180,8 +191,9 @@ private void appendMeshNode(List list, Node node)
/**
* Creates a new scene graph by parsing nodes contained in s-expression
*/
- public SceneGraph(SExp exp)
+ public SceneGraph(List exp, boolean rsmp)
{
+ this.generatedFromRSMP = rsmp;
root = new BaseNode();
readNodes(root, exp);
}
@@ -189,23 +201,21 @@ public SceneGraph(SExp exp)
/**
* Updates scene graph with new information. The structure of the scene graph remains unchanged.
*/
- public void update(SExp exp)
+ public void update(List exp)
{
+ if (exp == null)
+ return;
root.update(exp);
}
/**
* Recursive method that reads nodes from expression and adds them to parent
*/
- private void readNodes(Node parent, SExp exp)
+ private void readNodes(Node parent, List exp)
{
- // if there are no children expressions, the parent node must be a leaf
- ArrayList subExpressions = exp.getChildren();
- if (subExpressions == null)
+ if (exp == null)
return;
-
- // otherwise, there may be nodes to parse and add to the parent node
- for (SExp e : subExpressions) {
+ for (SExp e : exp) {
// each node declaration starts with "nd" followed by its type
String[] atoms = e.getAtoms();
if (atoms[0].equals(Node.DECL_ABRV)) {
@@ -224,6 +234,9 @@ private void readNodes(Node parent, SExp exp)
case SingleMaterialNode.EXP_ABRV:
node = new SingleMaterialNode(parent, e);
break;
+ case DescriptionNode.EXP_ABRV:
+ node = new DescriptionNode(parent, e);
+ break;
}
if (node != null) {
@@ -232,7 +245,7 @@ private void readNodes(Node parent, SExp exp)
parent.children.add(node);
// keep reading child's branch of nodes recursively
- readNodes(node, e);
+ readNodes(node, e.getChildren());
}
}
}
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/StaticMeshNode.java b/src/main/java/rv/comm/rcssserver/scenegraph/StaticMeshNode.java
index ab81e316..ae9175b7 100644
--- a/src/main/java/rv/comm/rcssserver/scenegraph/StaticMeshNode.java
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/StaticMeshNode.java
@@ -32,7 +32,7 @@ public class StaticMeshNode extends GeometryNode
public StaticMeshNode(Node parent, SExp exp)
{
- super(parent, exp);
+ super(parent, exp.getChildren());
s = exp.toString();
// (nd StaticMesh (load ) (sSc ) (setVisible 1)
// (setTransparent) (resetMaterials ))
diff --git a/src/main/java/rv/comm/rcssserver/scenegraph/TransformNode.java b/src/main/java/rv/comm/rcssserver/scenegraph/TransformNode.java
index 49cfdfd5..1af9a61a 100644
--- a/src/main/java/rv/comm/rcssserver/scenegraph/TransformNode.java
+++ b/src/main/java/rv/comm/rcssserver/scenegraph/TransformNode.java
@@ -16,6 +16,7 @@
package rv.comm.rcssserver.scenegraph;
+import java.util.List;
import jsgl.math.vector.Matrix;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -63,10 +64,10 @@ private void setMatrix(String[] atoms)
}
@Override
- public void update(SExp exp)
+ public void update(List exp)
{
- if (exp.getChildren() != null) {
- setMatrix(exp.getChildren().get(0).getAtoms());
+ if (exp != null) {
+ setMatrix(exp.get(0).getAtoms());
}
super.update(exp);
}
diff --git a/src/main/java/rv/content/ContentManager.java b/src/main/java/rv/content/ContentManager.java
index 021770b9..7fe87a90 100644
--- a/src/main/java/rv/content/ContentManager.java
+++ b/src/main/java/rv/content/ContentManager.java
@@ -168,6 +168,19 @@ public static void renderSelection(GL2 gl, Vec3f p, float r, float[] color, floa
Texture2D.unbind(gl);
}
+ private void loadMaterials(String path)
+ {
+ ClassLoader cl = getClass().getClassLoader();
+ InputStream is = getClass().getResourceAsStream(path);
+ assert is != null;
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ try {
+ naoMaterialLib.load(br, "textures/", cl);
+ } catch (IOException e) {
+ LOGGER.error("Unable to load {} material library: {}", path, e);
+ }
+ }
+
public boolean init(GLAutoDrawable drawable, GLInfo glInfo)
{
// use VBOs if they are supported
@@ -190,16 +203,10 @@ public boolean init(GLAutoDrawable drawable, GLInfo glInfo)
if (selectionTextureThin == null)
return false;
- // load nao materials
+ // load materials
naoMaterialLib = new ObjMaterialLibrary();
- ClassLoader cl = getClass().getClassLoader();
- InputStream is = getClass().getResourceAsStream("/materials/nao.mtl");
- BufferedReader br = new BufferedReader(new InputStreamReader(is));
- try {
- naoMaterialLib.load(br, "textures/", cl);
- } catch (IOException e) {
- LOGGER.error("Unable to load Nao material library", e);
- }
+ loadMaterials("/materials/nao.mtl");
+ loadMaterials("/materials/rcssservermj_default.mtl");
for (ObjMaterial m : naoMaterialLib.getMaterials())
m.init(drawable.getGL().getGL2());
@@ -356,4 +363,9 @@ public void gsMeasuresAndRulesChanged(GameState gs)
public void gsTimeChanged(GameState gs)
{
}
+
+ public synchronized void requestModelInitialization(Model model)
+ {
+ modelsToInitialize.add(model);
+ }
}
diff --git a/src/main/java/rv/content/Model.java b/src/main/java/rv/content/Model.java
index 65f97ac9..ee927b28 100644
--- a/src/main/java/rv/content/Model.java
+++ b/src/main/java/rv/content/Model.java
@@ -25,6 +25,7 @@
import jsgl.jogl.model.MeshPart;
import jsgl.jogl.model.ObjMaterial;
import jsgl.jogl.model.ObjMeshImporter;
+import jsgl.jogl.model.StdMeshImporter;
import jsgl.jogl.model.StlImporter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -70,30 +71,44 @@ public Model(String name)
this.name = name;
}
+ /**
+ * Creates a new model
+ *
+ * @param name model name
+ * @param mesh the mesh of the model
+ */
+ public Model(String name, Mesh mesh)
+ {
+ this.name = name;
+ this.mesh = mesh;
+ }
+
public void readMeshData(ContentManager cm)
{
- MeshImporter importer;
- if (name.toLowerCase().endsWith(".stl")) {
- importer = new StlImporter(ContentManager.MODEL_ROOT, ContentManager.MATERIAL_ROOT);
- } else {
- importer = new ObjMeshImporter(
- ContentManager.MODEL_ROOT, ContentManager.MATERIAL_ROOT, ContentManager.TEXTURE_ROOT);
- }
- // TODO: support loading standard meshes: StdUnitBox, StdUnitCylinder, ...
+ mesh = StdMeshImporter.generate(name);
+ if (mesh == null) {
+ MeshImporter importer;
+ if (name.toLowerCase().endsWith(".stl")) {
+ importer = new StlImporter(ContentManager.MODEL_ROOT, ContentManager.MATERIAL_ROOT);
+ } else {
+ importer = new ObjMeshImporter(
+ ContentManager.MODEL_ROOT, ContentManager.MATERIAL_ROOT, ContentManager.TEXTURE_ROOT);
+ }
- ClassLoader cl = this.getClass().getClassLoader();
- importer.setClassLoader(cl);
+ ClassLoader cl = this.getClass().getClassLoader();
+ importer.setClassLoader(cl);
- InputStream is = cl.getResourceAsStream(name);
- if (is == null) {
- failureMessage();
- return;
- }
- mesh = null;
- try {
- mesh = importer.loadMesh(is);
- } catch (IOException e) {
- failureMessage();
+ InputStream is = cl.getResourceAsStream(name);
+ if (is == null) {
+ failureMessage();
+ return;
+ }
+
+ try {
+ mesh = importer.loadMesh(is);
+ } catch (IOException e) {
+ failureMessage();
+ }
}
// this is necessary for the shader to blend meshes that have
@@ -132,6 +147,18 @@ public void replaceMaterial(String target, ObjMaterial src)
}
}
+ /** Copies the simplified rgba material into all materials of this model */
+ public void setRGBA(float[] rgba)
+ {
+ for (MeshPart part : mesh.getParts()) {
+ if (part.getMaterial() instanceof ObjMaterial mat) {
+ mat.setAmbient(rgba);
+ mat.setDiffuse(rgba);
+ mat.setIlluminationModel(2);
+ }
+ }
+ }
+
public void init(GL2 gl, Mesh.RenderMode mode)
{
if (!loaded && mesh != null) {
diff --git a/src/main/java/rv/ui/screens/FoulListOverlay.java b/src/main/java/rv/ui/screens/FoulListOverlay.java
index 5af4d200..a7093101 100644
--- a/src/main/java/rv/ui/screens/FoulListOverlay.java
+++ b/src/main/java/rv/ui/screens/FoulListOverlay.java
@@ -96,6 +96,8 @@ void drawFoul(GL2 gl, int x, int y, int w, int h, int screenW, int screenH, Game
case CHARGING:
case SELF_COLLISION:
case BALL_HOLDING:
+ case DOUBLE_TOUCH:
+ case HAND_FOUL:
default:
// Yellow
cardFillColor = new float[] {0.8f, 0.6f, 0.0f, 1.0f};
diff --git a/src/main/java/rv/ui/screens/LiveGameScreen.java b/src/main/java/rv/ui/screens/LiveGameScreen.java
index a0d10dfd..0fba6486 100644
--- a/src/main/java/rv/ui/screens/LiveGameScreen.java
+++ b/src/main/java/rv/ui/screens/LiveGameScreen.java
@@ -42,6 +42,7 @@ public LiveGameScreen()
super();
ServerSpeedBenchmarker ssb = new ServerSpeedBenchmarker();
Renderer.world.getGameState().addListener(ssb);
+ Renderer.world.addGlobalTimeListener(ssb);
Renderer.netManager.getServer().addChangeListener(this);
Renderer.netManager.getServer().addChangeListener(ssb);
gameStateOverlay.addServerSpeedBenchmarker(ssb);
diff --git a/src/main/java/rv/ui/view/TargetTrackerCamera.java b/src/main/java/rv/ui/view/TargetTrackerCamera.java
index abc8f55d..aa3838eb 100644
--- a/src/main/java/rv/ui/view/TargetTrackerCamera.java
+++ b/src/main/java/rv/ui/view/TargetTrackerCamera.java
@@ -118,15 +118,16 @@ private Vec3f offsetTargetPosition(Vec3f targetPos)
float halfLength = gs.getFieldLength() / 2;
float halfWidth = gs.getFieldWidth() / 2;
- float zoom = target instanceof Ball ? 1 : 4;
+ boolean isBall = target instanceof Ball;
+ float zoom = isBall ? 1 : 4;
- float xOffset = 4 * fuzzyValue(targetPos.x, -halfLength, halfLength);
- float baseZOffset = -8 / zoom;
- float zOffset = baseZOffset + 3 * fuzzyValue(targetPos.z, -halfWidth, halfWidth);
+ float xOffset = 0.2f * halfLength * fuzzyValue(targetPos.x, -halfLength, halfLength);
+ float baseZOffset = -0.8f * halfWidth / zoom;
+ float zOffset = baseZOffset + 0.3f * halfWidth * fuzzyValue(targetPos.z, -halfWidth, halfWidth);
Vec3f offsetPos = targetPos.clone();
offsetPos.add(Vec3f.unitX().times(xOffset));
- offsetPos.add(Vec3f.unitY().times(4 / zoom));
+ offsetPos.add(Vec3f.unitY().times(0.4f * halfWidth / zoom));
offsetPos.add(Vec3f.unitZ().times(zOffset));
return offsetPos;
}
@@ -136,7 +137,7 @@ private float fuzzyValue(float value, float lower, float upper)
if (value <= lower)
return 1;
if (value >= upper)
- return 0;
+ return -1;
return weight(1 - ((value - lower) / (upper - lower)));
}
diff --git a/src/main/java/rv/world/ModelObject.java b/src/main/java/rv/world/ModelObject.java
index 4b3db2b0..c707c0af 100644
--- a/src/main/java/rv/world/ModelObject.java
+++ b/src/main/java/rv/world/ModelObject.java
@@ -30,7 +30,7 @@
*/
public class ModelObject
{
- protected final Model model;
+ protected Model model;
protected Matrix modelMatrix = Matrix.createIdentity();
protected BoundingBox bounds;
diff --git a/src/main/java/rv/world/Team.java b/src/main/java/rv/world/Team.java
index d636ae49..5515f83d 100644
--- a/src/main/java/rv/world/Team.java
+++ b/src/main/java/rv/world/Team.java
@@ -25,6 +25,7 @@
import rv.comm.rcssserver.GameState;
import rv.comm.rcssserver.GameState.GameStateChangeListener;
import rv.comm.rcssserver.ISceneGraphItem;
+import rv.comm.rcssserver.scenegraph.DescriptionNode;
import rv.comm.rcssserver.scenegraph.Node;
import rv.comm.rcssserver.scenegraph.SceneGraph;
import rv.comm.rcssserver.scenegraph.StaticMeshNode;
@@ -161,6 +162,9 @@ private Node findAgent(int agentID, SceneGraph sg)
String matTeamID = id == Team.LEFT ? "matLeft" : "matRight";
String[] materials = {matAgentID, matTeamID};
+ if (sg.isGeneratedFromRSMP())
+ return findRSMPAgent(sg.getRoot(), agentID);
+
// see if a node can be found with these materials
StaticMeshNode leaf = sg.findStaticMeshNode(sg.getRoot(), materials);
if (leaf == null)
@@ -177,6 +181,29 @@ private Node findAgent(int agentID, SceneGraph sg)
return root;
}
+ private Node findRSMPAgent(Node n, int agentID)
+ {
+ if (n.getChildren() == null || n.getChildren().isEmpty())
+ return null;
+
+ if (n instanceof DescriptionNode dsc) {
+ for (var d : dsc.getDescriptions()) {
+ if (d.length == 3 && d[0].equals("agent")) {
+ if (d[1].equals(name) && d[2].equals(Integer.toString(agentID))) {
+ return n.getChildren().get(0);
+ } else
+ return null; // this is the wrong agent
+ }
+ }
+ }
+ for (var child : n.getChildren()) {
+ final var root = findRSMPAgent(child, agentID);
+ if (root != null)
+ return root;
+ }
+ return null;
+ }
+
@Override
public void gsMeasuresAndRulesChanged(GameState gs)
{
diff --git a/src/main/java/rv/world/WorldModel.java b/src/main/java/rv/world/WorldModel.java
index 4acea36e..176c67bc 100644
--- a/src/main/java/rv/world/WorldModel.java
+++ b/src/main/java/rv/world/WorldModel.java
@@ -25,12 +25,15 @@
import jsgl.jogl.light.LightModel;
import jsgl.math.vector.Matrix;
import jsgl.math.vector.Vec3f;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.magmaoffenburg.roboviz.configuration.Config;
import org.magmaoffenburg.roboviz.configuration.Config.TeamColors;
import org.magmaoffenburg.roboviz.rendering.CameraController;
import org.magmaoffenburg.roboviz.util.Mode;
import rv.comm.rcssserver.GameState;
import rv.comm.rcssserver.ISceneGraphItem;
+import rv.comm.rcssserver.ProtocolVersion;
import rv.comm.rcssserver.scenegraph.SceneGraph;
import rv.comm.rcssserver.scenegraph.SceneGraph.SceneGraphListener;
import rv.content.ContentManager;
@@ -51,11 +54,19 @@ public interface SelectionChangeListener
void selectionChanged(ISelectable newSelection);
}
+ public interface GlobalTimeListener
+ {
+ void globalTimeChanged(float newGlobalTime);
+ }
+
/** Transforms SimSpark coordinates to RoboViz coordinates (and reverse) */
public static final Matrix COORD_TFN = new Matrix(new double[] {-1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1});
+ private static final Logger LOGGER = LogManager.getLogger();
+
private final GameState gameState = new GameState();
private SceneGraph sceneGraph = null;
+ private float globalTime;
private ContentManager cm;
private final ArrayList sgItems = new ArrayList<>();
@@ -71,6 +82,7 @@ public interface SelectionChangeListener
private float ballCircleTimeLeft;
private float ballCircleTime;
+ private final ArrayList gtListeners = new ArrayList<>();
private final ArrayList sgListeners = new ArrayList<>();
private final ArrayList selListeners = new ArrayList<>();
@@ -94,6 +106,16 @@ public void removeSelectionChangeListener(SelectionChangeListener sl)
selListeners.remove(sl);
}
+ public void addGlobalTimeListener(GlobalTimeListener gtl)
+ {
+ gtListeners.add(gtl);
+ }
+
+ public void removeGlobalTimeListener(GlobalTimeListener gtl)
+ {
+ gtListeners.remove(gtl);
+ }
+
public ISelectable getSelectedObject()
{
return selectedObject;
@@ -154,6 +176,22 @@ public synchronized void setSceneGraph(SceneGraph sceneGraph)
}
}
+ public float getGlobalTime()
+ {
+ return globalTime;
+ }
+
+ public void updateGlobalTime(String[] sexp, ProtocolVersion version)
+ {
+ if (!version.supports(1, 0)) {
+ LOGGER.error("unsupported global time version: {}", version);
+ return;
+ }
+ this.globalTime = Float.parseFloat(sexp[0]);
+ for (GlobalTimeListener gtl : gtListeners)
+ gtl.globalTimeChanged(this.globalTime);
+ }
+
public LightModel getLighting()
{
return lighting;
@@ -189,7 +227,7 @@ public void init(GL glObj, ContentManager cm, Mode mode)
this.cm = cm;
GL2 gl = glObj.getGL2();
- field = new Field(cm.getModel("models/newfield.obj"), cm);
+ field = new Field(cm, gameState.getFieldLength(), gameState.getFieldWidth());
gameState.addListener(field);
gameState.addListener(cm);
@@ -254,9 +292,13 @@ public synchronized void update(GL gl, double elapsedMS)
public void renderBallCircle(GL2 gl)
{
+ var playMode = gameState.getPlayMode();
+ if (playMode == null)
+ return;
+
if (gameState.hasPlayModeJustChanged()) {
// just switched
- switch (gameState.getPlayMode()) {
+ switch (playMode) {
case GameState.PASS_LEFT:
case GameState.PASS_RIGHT:
ballCircleTime = ballCircleTimeLeft = gameState.getPassModeDuration();
@@ -273,7 +315,7 @@ public void renderBallCircle(GL2 gl)
}
Color color = null;
- switch (gameState.getPlayMode()) {
+ switch (playMode) {
case GameState.PASS_LEFT:
case GameState.KICK_IN_LEFT:
case GameState.CORNER_KICK_LEFT:
@@ -289,7 +331,7 @@ public void renderBallCircle(GL2 gl)
}
float radius = 0;
- switch (gameState.getPlayMode()) {
+ switch (playMode) {
case GameState.PASS_LEFT:
case GameState.PASS_RIGHT:
radius = gameState.getPassModeMinOppBallDist();
@@ -325,5 +367,6 @@ public synchronized void reset()
if (cm != null)
initTeams();
setSceneGraph(null);
+ globalTime = 0;
}
}
diff --git a/src/main/java/rv/world/objects/Agent.java b/src/main/java/rv/world/objects/Agent.java
index 3e2eb329..5392459b 100644
--- a/src/main/java/rv/world/objects/Agent.java
+++ b/src/main/java/rv/world/objects/Agent.java
@@ -136,6 +136,13 @@ public Agent(Team team, int id, Node rootNode, SceneGraph sg, ContentManager cm)
}
}
+ private enum RobotType
+ {
+ NAO,
+ T1,
+ K1
+ }
+
/**
* Grabs model matrices from scene graph and updates bounding box
*/
@@ -145,21 +152,38 @@ public void update(SceneGraph sg)
Vec3f max = new Vec3f(Float.NEGATIVE_INFINITY);
for (StaticMeshNode node : meshNodes) {
- Model model = content.getModel(node.getName());
+ var nodeName = node.getName();
+ var robotType = RobotType.NAO;
+ if (nodeName.contains("T1/"))
+ robotType = RobotType.T1;
+ else if (nodeName.contains("K1/"))
+ robotType = RobotType.K1;
+
+ Model model = content.getModel(nodeName);
if (model.isLoaded()) {
Vec3f[] corners = model.getMesh().getBounds().getCorners();
Matrix modelMat = WorldModel.COORD_TFN.times(node.getWorldTransform());
// store head transformation for "robot perspective" camera mode
- if (node.getName().endsWith("head.obj")) {
+ if (nodeName.endsWith("head.obj") || (robotType == RobotType.T1 && nodeName.endsWith("H2.STL")) ||
+ (robotType == RobotType.K1 && nodeName.contains("Head_2"))) {
headTransform = modelMat;
headCenter = headTransform.transform(new Vec3f(0));
- headDirection = headTransform.transform(new Vec3f(0, 0, 1)).minus(headCenter).normalize();
- } else if (node.getName().matches(".*body.*[.]obj$")) {
+ headDirection = headTransform
+ .transform(robotType == Agent.RobotType.NAO ? new Vec3f(0, 0, 1)
+ : new Vec3f(1, 0, 0))
+ .minus(headCenter)
+ .normalize();
+ } else if (nodeName.matches(".*body.*[.]obj$") ||
+ ((robotType == RobotType.T1 || robotType == RobotType.K1) &&
+ nodeName.endsWith("Trunk.STL"))) {
// Store body direction for third person view
Matrix bodyRot = modelMat;
Vec3f bodyCenter = bodyRot.transform(new Vec3f(0));
- torsoDirection = bodyRot.transform(new Vec3f(0, 0, 1)).minus(bodyCenter).normalize();
+ torsoDirection = bodyRot.transform(robotType == Agent.RobotType.NAO ? new Vec3f(0, 0, 1)
+ : new Vec3f(1, 0, 0))
+ .minus(bodyCenter)
+ .normalize();
}
for (int j = 0; j < 8; j++) {
diff --git a/src/main/java/rv/world/objects/Ball.java b/src/main/java/rv/world/objects/Ball.java
index b2313658..de06e34d 100644
--- a/src/main/java/rv/world/objects/Ball.java
+++ b/src/main/java/rv/world/objects/Ball.java
@@ -21,6 +21,8 @@
import jsgl.math.vector.Matrix;
import jsgl.math.vector.Vec3f;
import rv.comm.rcssserver.ISceneGraphItem;
+import rv.comm.rcssserver.scenegraph.DescriptionNode;
+import rv.comm.rcssserver.scenegraph.Node;
import rv.comm.rcssserver.scenegraph.SceneGraph;
import rv.comm.rcssserver.scenegraph.StaticMeshNode;
import rv.content.ContentManager;
@@ -43,7 +45,31 @@ public Ball(ContentManager content)
@Override
public void sceneGraphChanged(SceneGraph sg)
{
- node = sg.findStaticMeshNode("soccerball.obj");
+ if (sg.isGeneratedFromRSMP()) {
+ node = findBall(sg.getRoot());
+ } else {
+ node = sg.findStaticMeshNode("soccerball.obj");
+ }
+ }
+
+ private static StaticMeshNode findBall(Node n)
+ {
+ if (n.getChildren() == null || n.getChildren().isEmpty())
+ return null;
+
+ if (n instanceof DescriptionNode dsc) {
+ for (var d : dsc.getDescriptions()) {
+ if (d.length == 1 && d[0].equals("ball")) {
+ return n.findNodeWithType(StaticMeshNode.class, true);
+ }
+ }
+ }
+ for (var child : n.getChildren()) {
+ final var ballNode = findBall(child);
+ if (ballNode != null)
+ return ballNode;
+ }
+ return null;
}
@Override
diff --git a/src/main/java/rv/world/objects/Field.java b/src/main/java/rv/world/objects/Field.java
index e9fef877..828f9af6 100644
--- a/src/main/java/rv/world/objects/Field.java
+++ b/src/main/java/rv/world/objects/Field.java
@@ -18,11 +18,22 @@
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
import jsgl.jogl.GLDisposable;
import jsgl.jogl.Texture2D;
import jsgl.jogl.light.Material;
+import jsgl.jogl.model.Mesh;
+import jsgl.jogl.model.MeshFace;
+import jsgl.jogl.model.MeshPart;
+import jsgl.jogl.model.MeshVertex;
+import jsgl.jogl.model.ObjMaterial;
+import jsgl.math.BoundingBox;
+import jsgl.math.PerlinNoise;
import jsgl.math.vector.Matrix;
+import jsgl.math.vector.Vec2f;
import jsgl.math.vector.Vec3f;
+import org.apache.commons.lang3.ArrayUtils;
import rv.comm.rcssserver.GameState;
import rv.comm.rcssserver.GameState.GameStateChangeListener;
import rv.content.ContentManager;
@@ -48,19 +59,102 @@ public class Field extends ModelObject implements GameStateChangeListener, GLDis
private static final float GOAL_BOX_LENGTH = 1.8f;
private static final float LINE_THICKNESS = 0.02f;
+ protected ContentManager contentManager;
+
private final Material lineMaterial = new Material();
private float[][] circleVerts;
private float[][] lineVerts;
private int[][] lineIndices;
private boolean geometryUpdated = false;
+ private Vec2f fieldDimensions;
+ private Model newModel;
private int linesDisplayList;
private boolean disposed = false;
private Texture2D lineTexture;
- public Field(Model model, ContentManager cm)
+ public Field(ContentManager cm, float l, float w)
{
- super(model);
+ super(generateFieldModel(cm, l, w));
+ contentManager = cm;
lineTexture = cm.getWhiteTexture();
+ fieldDimensions = new Vec2f(l, w);
+ }
+
+ private static Model generateFieldModel(ContentManager cm, float l, float w)
+ {
+ final Mesh mesh = new Mesh();
+ final MeshPart part = new MeshPart();
+
+ final float hl = l / 2;
+ final float hw = w / 2;
+
+ final var n = new float[] {0, 1, 0};
+
+ final var v1 = new MeshVertex(new float[] {-hl - 2, 0, hw + 2}, n, new float[] {0, 0, 0});
+ final var v2 = new MeshVertex(new float[] {hl + 2, 0, hw + 2}, n, new float[] {1, 0, 0});
+ final var v3 = new MeshVertex(new float[] {hl + 2, 0, -hw - 2}, n, new float[] {1, 1, 0});
+ final var v4 = new MeshVertex(new float[] {-hl - 2, 0, -hw - 2}, n, new float[] {0, 1, 0});
+
+ mesh.addVertex(v1);
+ mesh.addVertex(v2);
+ mesh.addVertex(v3);
+ mesh.addVertex(v4);
+
+ part.addFace(new MeshFace(new int[] {0, 2, 3}));
+ part.addFace(new MeshFace(new int[] {0, 1, 2}));
+
+ var name = "field-" + l + "-" + w;
+ var material = new ObjMaterial(name);
+ material.setTextureImage(generateTexture(l, w));
+ part.setMaterial(material);
+
+ mesh.addPart(part);
+ mesh.setBounds(new BoundingBox(new Vec3f(-hl - 2, 0, -hw - 2), new Vec3f(hl + 2, 0, hw + 2)));
+
+ var model = new Model(name, mesh);
+ cm.requestModelInitialization(model);
+ return model;
+ }
+
+ private static BufferedImage generateTexture(float l, float w)
+ {
+ // Add field border
+ l += 4;
+ w += 4;
+
+ final int width = Math.round(2000 / w * l);
+ final int height = 2000;
+
+ var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ float xReal = (float) x / width * l;
+ float yReal = (float) y / height * w;
+ float noise = PerlinNoise.noise3(xReal, yReal, 0, 0, 0, 0);
+ int r;
+ int g;
+ int b;
+ if (xReal < 2 || xReal > l - 2 || yReal < 2 || yReal > w - 2) {
+ // Border
+ r = (int) (160 + noise * 20);
+ g = (int) (220 + noise * 25);
+ b = (int) (80 + noise * 20);
+ } else if ((int) xReal % 2 == 1) {
+ // Stripe 2
+ r = (int) (100 + noise * 25);
+ g = (int) (190 + noise * 35);
+ b = (int) (45 + noise * 20);
+ } else {
+ // Stripe 1
+ r = (int) (120 + noise * 25);
+ g = (int) (210 + noise * 35);
+ b = (int) (45 + noise * 20);
+ }
+ pixels[width * y + x] = (r << 16) | (g << 8) | b;
+ }
+ }
+ return img;
}
/** Creates the field lines based on dimensions in game state */
@@ -70,8 +164,20 @@ private void calculateLineGeometry(GameState gs)
float hfw = gs.getFieldWidth() / 2.0f;
float goalWidth = GOAL_BOX_WIDTH + PENALTY_WIDTH;
float goalLength = GOAL_BOX_LENGTH + PENALTY_LENGTH;
+ if (gs.getGoalieAreaLength() > 0) {
+ goalWidth = gs.getGoalieAreaWidth();
+ goalLength = gs.getGoalieAreaLength();
+ }
float hgw = goalWidth / 2.0f;
float hgl = goalLength / 2.0f;
+ float penaltyAreaWidth = 0;
+ float penaltyAreaLength = 0;
+ if (gs.getPenaltyAreaLength().isPresent()) {
+ penaltyAreaWidth = gs.getPenaltyAreaWidth().get();
+ penaltyAreaLength = gs.getPenaltyAreaLength().get();
+ }
+ final float hpw = penaltyAreaWidth / 2.0f;
+ final float hpl = penaltyAreaLength / 2.0f;
lineVerts = new float[][] {
// border lines
@@ -109,14 +215,39 @@ private void calculateLineGeometry(GameState gs)
{hfl, 0, -hgw - LINE_THICKNESS},
{hfl - hgl - LINE_THICKNESS, 0, -hgw - LINE_THICKNESS},
{hfl - hgl - LINE_THICKNESS, 0, hgw + LINE_THICKNESS},
+
+ // right penalty area
+ {-hfl, 0, hpw + LINE_THICKNESS},
+ {-hfl, 0, hpw - LINE_THICKNESS},
+ {-hfl + hpl - LINE_THICKNESS, 0, hpw - LINE_THICKNESS},
+ {-hfl + hpl - LINE_THICKNESS, 0, -hpw + LINE_THICKNESS},
+ {-hfl, 0, -hpw + LINE_THICKNESS},
+ {-hfl, 0, -hpw - LINE_THICKNESS},
+ {-hfl + hpl + LINE_THICKNESS, 0, -hpw - LINE_THICKNESS},
+ {-hfl + hpl + LINE_THICKNESS, 0, hpw + LINE_THICKNESS},
+
+ // left penalty area
+ {hfl, 0, hpw + LINE_THICKNESS},
+ {hfl, 0, hpw - LINE_THICKNESS},
+ {hfl - hpl + LINE_THICKNESS, 0, hpw - LINE_THICKNESS},
+ {hfl - hpl + LINE_THICKNESS, 0, -hpw + LINE_THICKNESS},
+ {hfl, 0, -hpw + LINE_THICKNESS},
+ {hfl, 0, -hpw - LINE_THICKNESS},
+ {hfl - hpl - LINE_THICKNESS, 0, -hpw - LINE_THICKNESS},
+ {hfl - hpl - LINE_THICKNESS, 0, hpw + LINE_THICKNESS},
};
lineIndices =
new int[][] {{0, 1, 5, 4}, {1, 2, 6, 5}, {2, 3, 7, 6}, {3, 0, 4, 7}, {8, 9, 10, 11}, {12, 13, 14, 19},
{19, 14, 15, 18}, {15, 16, 17, 18}, {20, 21, 22, 27}, {27, 22, 23, 26}, {23, 24, 25, 26}};
+ if (gs.getPenaltyAreaLength().isPresent()) {
+ lineIndices =
+ ArrayUtils.addAll(lineIndices, new int[][] {{28, 29, 30, 35}, {35, 30, 31, 34}, {31, 32, 33, 34},
+ {36, 37, 38, 43}, {43, 38, 39, 42}, {39, 40, 41, 42}});
+ }
// center circle
- float radius = gs.getFreeKickDistance();
+ float radius = gs.getCenterCircleRadius();
circleVerts = new float[CIRCLE_SEGMENTS * 2][3];
double angleInc = Math.PI * 2.0 / CIRCLE_SEGMENTS;
int j = 0;
@@ -154,6 +285,13 @@ private void renderLines(GL2 gl)
public void render(GL2 gl)
{
+ if (newModel != null) {
+ model.dispose(gl);
+ model = newModel;
+ newModel = null;
+ model.init(gl, contentManager.getMeshRenderMode());
+ }
+
super.render(gl);
if (geometryUpdated) {
@@ -170,9 +308,19 @@ public void render(GL2 gl)
gl.glCallList(linesDisplayList);
}
+ private void updateModel(GameState gs)
+ {
+ var newDimensions = new Vec2f(gs.getFieldLength(), gs.getFieldWidth());
+ if (newDimensions.x != fieldDimensions.x || newDimensions.y != fieldDimensions.y) {
+ fieldDimensions = newDimensions;
+ newModel = generateFieldModel(contentManager, fieldDimensions.x, fieldDimensions.y);
+ }
+ }
+
@Override
public void gsMeasuresAndRulesChanged(GameState gs)
{
+ updateModel(gs);
calculateLineGeometry(gs);
}
@@ -190,6 +338,7 @@ public void gsTimeChanged(GameState gs)
public void dispose(GL gl)
{
gl.getGL2().glDeleteLists(linesDisplayList, 1);
+ model.dispose(gl);
disposed = true;
}
diff --git a/src/main/java/rv/world/rendering/BasicSceneRenderer.java b/src/main/java/rv/world/rendering/BasicSceneRenderer.java
index 11cfd342..2f32a8c8 100644
--- a/src/main/java/rv/world/rendering/BasicSceneRenderer.java
+++ b/src/main/java/rv/world/rendering/BasicSceneRenderer.java
@@ -53,10 +53,20 @@ public boolean init(GL2 gl2, Config.Graphics conf, ContentManager cm)
public static void applySingleMat(Model model, StaticMeshNode node, ContentManager content)
{
- if (node instanceof SingleMaterialNode && node.getMaterials().length > 0) {
- var mat = content.getMaterial(node.getMaterials()[0]);
- if (mat != null)
- model.replaceMaterial("Default", mat);
+ if (node instanceof SingleMaterialNode) {
+ if (node.getMaterials().length > 0) {
+ final String matName = node.getMaterials()[0];
+ var mat = content.getMaterial(matName);
+ if (mat != null) {
+ model.replaceMaterial("Default", mat);
+ return;
+ }
+ }
+
+ var rgba = node.getRGBA();
+ if (rgba != null) {
+ model.setRGBA(rgba);
+ }
}
}
diff --git a/src/main/kotlin/org/magmaoffenburg/roboviz/configuration/Config.kt b/src/main/kotlin/org/magmaoffenburg/roboviz/configuration/Config.kt
index 9317103b..c86a37bf 100644
--- a/src/main/kotlin/org/magmaoffenburg/roboviz/configuration/Config.kt
+++ b/src/main/kotlin/org/magmaoffenburg/roboviz/configuration/Config.kt
@@ -77,7 +77,7 @@ class Config(args: Array) {
var servers = arrayListOf>()
var defaultServerHost = "localhost"
- var defaultServerPort = 3200
+ var defaultServerPort = 60001
var currentHost = defaultServerHost
var currentPort = defaultServerPort
diff --git a/src/main/kotlin/org/magmaoffenburg/roboviz/gui/dialogs/ServerListDialog.kt b/src/main/kotlin/org/magmaoffenburg/roboviz/gui/dialogs/ServerListDialog.kt
index 3ceef358..5f5c61e8 100644
--- a/src/main/kotlin/org/magmaoffenburg/roboviz/gui/dialogs/ServerListDialog.kt
+++ b/src/main/kotlin/org/magmaoffenburg/roboviz/gui/dialogs/ServerListDialog.kt
@@ -74,7 +74,7 @@ object ServerListDialog : JDialog() {
private fun initializeActions() {
addButton.addActionListener {
- tableModel.addRow(arrayOf("localhost", 3200))
+ tableModel.addRow(arrayOf("localhost", 60001))
pack()
}
removeButton.addActionListener {
@@ -93,7 +93,7 @@ object ServerListDialog : JDialog() {
val port = when (val portAny = tableModel.getValueAt(i, 1)) {
is String -> portAny.toString().replace(Regex("[^\\d]"), "").toInt()
is Int -> portAny
- else -> 3200
+ else -> 60001
}
Networking.servers.add(Pair(key, port))
@@ -120,4 +120,4 @@ object ServerListDialog : JDialog() {
toFront()
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/materials/rcssservermj_default.mtl b/src/main/resources/materials/rcssservermj_default.mtl
new file mode 100644
index 00000000..a48e0d88
--- /dev/null
+++ b/src/main/resources/materials/rcssservermj_default.mtl
@@ -0,0 +1,35 @@
+newmtl metal
+Ns 96.078431
+Ka 0.900000 0.950000 0.950000
+Kd 0.900000 0.950000 0.950000
+Ks 0.930000 0.970000 0.970000
+Ni 1.000000
+d 1.000000
+illum 2
+
+newmtl black
+Ns 96.078431
+Ka 0.000000 0.000000 0.000000
+Kd 0.000000 0.000000 0.000000
+Ks 0.500000 0.500000 0.500000
+Ni 1.000000
+d 1.000000
+illum 2
+
+newmtl gray
+Ns 96.078431
+Ka 0.400000 0.400000 0.400000
+Kd 0.400000 0.400000 0.400000
+Ks 0.500000 0.500000 0.500000
+Ni 1.000000
+d 1.000000
+illum 2
+
+newmtl transparent
+Ns 96.078431
+Ka 0.752940 0.752940 0.752940
+Kd 0.752940 0.752940 0.752940
+Ks 0.500000 0.500000 0.500000
+Ni 1.000000
+d 1.000000
+illum 2
\ No newline at end of file
diff --git a/src/main/resources/models/K1/Ankle_Cross.STL b/src/main/resources/models/K1/Ankle_Cross.STL
new file mode 100644
index 00000000..6807ae48
Binary files /dev/null and b/src/main/resources/models/K1/Ankle_Cross.STL differ
diff --git a/src/main/resources/models/K1/Head_1.STL b/src/main/resources/models/K1/Head_1.STL
new file mode 100644
index 00000000..9b363d0f
Binary files /dev/null and b/src/main/resources/models/K1/Head_1.STL differ
diff --git a/src/main/resources/models/K1/Head_2.STL b/src/main/resources/models/K1/Head_2.STL
new file mode 100644
index 00000000..c22b6afe
Binary files /dev/null and b/src/main/resources/models/K1/Head_2.STL differ
diff --git a/src/main/resources/models/K1/Head_2_ZED.STL b/src/main/resources/models/K1/Head_2_ZED.STL
new file mode 100644
index 00000000..fc001e3a
Binary files /dev/null and b/src/main/resources/models/K1/Head_2_ZED.STL differ
diff --git a/src/main/resources/models/K1/Hip_Roll.STL b/src/main/resources/models/K1/Hip_Roll.STL
new file mode 100644
index 00000000..46247837
Binary files /dev/null and b/src/main/resources/models/K1/Hip_Roll.STL differ
diff --git a/src/main/resources/models/K1/K1logo.STL b/src/main/resources/models/K1/K1logo.STL
new file mode 100644
index 00000000..168ef8f5
Binary files /dev/null and b/src/main/resources/models/K1/K1logo.STL differ
diff --git a/src/main/resources/models/K1/LICENSE b/src/main/resources/models/K1/LICENSE
new file mode 100644
index 00000000..d7a14af3
--- /dev/null
+++ b/src/main/resources/models/K1/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2025, BoosterRobotics
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/main/resources/models/K1/Left_Arm_1.STL b/src/main/resources/models/K1/Left_Arm_1.STL
new file mode 100644
index 00000000..5032fb29
Binary files /dev/null and b/src/main/resources/models/K1/Left_Arm_1.STL differ
diff --git a/src/main/resources/models/K1/Left_Arm_2.STL b/src/main/resources/models/K1/Left_Arm_2.STL
new file mode 100644
index 00000000..202e54c9
Binary files /dev/null and b/src/main/resources/models/K1/Left_Arm_2.STL differ
diff --git a/src/main/resources/models/K1/Left_Arm_3.STL b/src/main/resources/models/K1/Left_Arm_3.STL
new file mode 100644
index 00000000..cc77fec0
Binary files /dev/null and b/src/main/resources/models/K1/Left_Arm_3.STL differ
diff --git a/src/main/resources/models/K1/Left_Arm_4.STL b/src/main/resources/models/K1/Left_Arm_4.STL
new file mode 100644
index 00000000..d361bde0
Binary files /dev/null and b/src/main/resources/models/K1/Left_Arm_4.STL differ
diff --git a/src/main/resources/models/K1/Left_Foot.STL b/src/main/resources/models/K1/Left_Foot.STL
new file mode 100644
index 00000000..242827b8
Binary files /dev/null and b/src/main/resources/models/K1/Left_Foot.STL differ
diff --git a/src/main/resources/models/K1/Left_Hip_Pitch.STL b/src/main/resources/models/K1/Left_Hip_Pitch.STL
new file mode 100644
index 00000000..cd2827c5
Binary files /dev/null and b/src/main/resources/models/K1/Left_Hip_Pitch.STL differ
diff --git a/src/main/resources/models/K1/Left_Hip_Yaw.STL b/src/main/resources/models/K1/Left_Hip_Yaw.STL
new file mode 100644
index 00000000..9f24efb3
Binary files /dev/null and b/src/main/resources/models/K1/Left_Hip_Yaw.STL differ
diff --git a/src/main/resources/models/K1/Left_Shank.STL b/src/main/resources/models/K1/Left_Shank.STL
new file mode 100644
index 00000000..da6dc7c1
Binary files /dev/null and b/src/main/resources/models/K1/Left_Shank.STL differ
diff --git a/src/main/resources/models/K1/Trunk.STL b/src/main/resources/models/K1/Trunk.STL
new file mode 100644
index 00000000..ec286f0b
Binary files /dev/null and b/src/main/resources/models/K1/Trunk.STL differ
diff --git a/src/main/resources/models/T1/AL1.STL b/src/main/resources/models/T1/AL1.STL
new file mode 100755
index 00000000..0693be32
Binary files /dev/null and b/src/main/resources/models/T1/AL1.STL differ
diff --git a/src/main/resources/models/T1/AL2.STL b/src/main/resources/models/T1/AL2.STL
new file mode 100755
index 00000000..3be09bac
Binary files /dev/null and b/src/main/resources/models/T1/AL2.STL differ
diff --git a/src/main/resources/models/T1/AL3.STL b/src/main/resources/models/T1/AL3.STL
new file mode 100755
index 00000000..b605c898
Binary files /dev/null and b/src/main/resources/models/T1/AL3.STL differ
diff --git a/src/main/resources/models/T1/Ankle_Cross_Left.STL b/src/main/resources/models/T1/Ankle_Cross_Left.STL
new file mode 100755
index 00000000..1be45499
Binary files /dev/null and b/src/main/resources/models/T1/Ankle_Cross_Left.STL differ
diff --git a/src/main/resources/models/T1/H1.STL b/src/main/resources/models/T1/H1.STL
new file mode 100755
index 00000000..24fd45dd
Binary files /dev/null and b/src/main/resources/models/T1/H1.STL differ
diff --git a/src/main/resources/models/T1/H2.STL b/src/main/resources/models/T1/H2.STL
new file mode 100755
index 00000000..b14dfb1f
Binary files /dev/null and b/src/main/resources/models/T1/H2.STL differ
diff --git a/src/main/resources/models/T1/Hip_Pitch_Left.STL b/src/main/resources/models/T1/Hip_Pitch_Left.STL
new file mode 100755
index 00000000..575936e1
Binary files /dev/null and b/src/main/resources/models/T1/Hip_Pitch_Left.STL differ
diff --git a/src/main/resources/models/T1/Hip_Roll.STL b/src/main/resources/models/T1/Hip_Roll.STL
new file mode 100755
index 00000000..e335084b
Binary files /dev/null and b/src/main/resources/models/T1/Hip_Roll.STL differ
diff --git a/src/main/resources/models/T1/Hip_Yaw.STL b/src/main/resources/models/T1/Hip_Yaw.STL
new file mode 100755
index 00000000..cf9eb06a
Binary files /dev/null and b/src/main/resources/models/T1/Hip_Yaw.STL differ
diff --git a/src/main/resources/models/T1/LICENSE b/src/main/resources/models/T1/LICENSE
new file mode 100644
index 00000000..c9e19b34
--- /dev/null
+++ b/src/main/resources/models/T1/LICENSE
@@ -0,0 +1,22 @@
+Copyright [2024] [Booster Robotics Technology Co., Ltd ("Booster Robotics")]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+------------------
+
+This code builds upon following open-source code-bases. Please visit the URLs to see the respective LICENSES:
+
+1) https://github.com/isaac-sim/IsaacGymEnvs
+2) https://github.com/leggedrobotics/legged_gym
+3) https://github.com/leggedrobotics/rsl_rl
+4) https://github.com/roboterax/humanoid-gym
diff --git a/src/main/resources/models/T1/Shank_Left.STL b/src/main/resources/models/T1/Shank_Left.STL
new file mode 100755
index 00000000..61dec296
Binary files /dev/null and b/src/main/resources/models/T1/Shank_Left.STL differ
diff --git a/src/main/resources/models/T1/Trunk.STL b/src/main/resources/models/T1/Trunk.STL
new file mode 100755
index 00000000..d27dfe2c
Binary files /dev/null and b/src/main/resources/models/T1/Trunk.STL differ
diff --git a/src/main/resources/models/T1/Waist.STL b/src/main/resources/models/T1/Waist.STL
new file mode 100755
index 00000000..ceb11980
Binary files /dev/null and b/src/main/resources/models/T1/Waist.STL differ
diff --git a/src/main/resources/models/T1/foot_link.STL b/src/main/resources/models/T1/foot_link.STL
new file mode 100755
index 00000000..43736478
Binary files /dev/null and b/src/main/resources/models/T1/foot_link.STL differ
diff --git a/src/main/resources/models/T1/left_hand_link.STL b/src/main/resources/models/T1/left_hand_link.STL
new file mode 100755
index 00000000..d6fe8a04
Binary files /dev/null and b/src/main/resources/models/T1/left_hand_link.STL differ