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: + *

+ * + * @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