Physics sims: which structures to use (primitives versus vectors, `double` versus `long`, `volatile` versus `synchronized` versus `Atomic*`)
Most software engines (both for games, or physics sims) stick to float (or double) for position structures, but not much discussion exists on which structure to use.
[Version of this post is SusuPosts@cdbcad2. The most new version is on this GitHub preview branch.]
Intro
This document uses java syntax for data structures plus simple physics functions (which all have close-to-similar versions in most languages).
Most software engines (both for games, or physics sims) stick to float (or double) for position structures, but not much discussion exists on which structure to use.
Unless the sim is supposed to allow simple future changes of the structures used, the chore is often difficult:
to switch from separate variables (such as
double xPos, yPos;) to dim lists (such asdouble[] pos),to switch all positions + distances from
doubletolong, orlongtodouble.
Popular structures for sims
Examples below all assume the default is public class.
types are primitives (such as double) or
classes (such as java.lang.Integer).Inheritance (”to inherit“) is for one
classto have all which some (different)class(orinterface) has. Non-primitive types injavaall inherit from java.lang.Object:class Node : extends Object { double value; };extendsObjectto includedouble value;.interfaces are incomplete types, similar to pure virtualclasses (classes which have unimplemented functions). Forinterfaces, useimplements(notextends):interface RandomAccess { public double at(int index); }; class ImmutableList implements RandomAccess { protected double[] values; @Override public double at(int index) { return values[index]; } };
Nodes (also known as elements) are types stored in lists (such as
double[] arrayListor java.util.List<Integer> list), or stored in graphs.Structures are nodes with some definition of usage (such as coordinates, vertices, meshes or textures).
Tensors are the superset of vectors (contiguous lists such as
double[]) which can include numerous dimensions (Color[][] bitmapis an example of 2-dimensional tensor).Resolution is most known to consumers as the
.size()of tensors:Color[1000][2000] bitmap;gives1000*2000resolution to the tensor known asbitmap(resolution of monitors is the.size()of thebitmapof thus monitors).Resolution has an overloaded definition: resolution also refers to the minimum step size (for
Integerthis is1, forDoublethis changes with magnitude, but epsilon values (such as Double.MIN_NORMAL are popular minimum step sizes forDouble, or you can use Math.nextAfter())). Most physics documents use the “minimum step size” definition of resolution (which the section double versus long uses).
Indices are offsets from the first node of lists:
class PrimitiveIndices { int[] index; }; class Index { Integer value; }; class Indices { List<Index> index; };Indicesstores aListofIndexes. WithPrimitiveIndices, performance improves, butjava‘s Generics can not use primitives.Edges are tuples of indices into lists, for adjacent nodes:
class Edge { Index source, dest; /* indices */}; class Edges { List<Edge> edge; };,Edgesstores aListofEdges. Thesource, destformat supports directed edges (some graphs require directed edges), but also allows undirected edges (meshes use undirected edges).Graphs are similar to lists but store connections (
Edges).Dense graphs are 2-dimensional lists of nodes (in meshes, thus nodes are vertices):
Index[nTotalVertices][nMaxAdjacentVertices] adjacencyList;is a dense graph ofIndexs (indices) to nodes.Sparse graphs are the most popular graphs, which are similar to dense graphs except just edges are stored:
one form of sparse graphs is
Edges, which stores unsortedEdges.another form of sparse graphs is
Map<Index, Indices> adjacencyList;, which is an associative list ofIndexs (indices) of nodes toLists of adjacent nodes. This uses more resources to store, but less resources fetch values.
Pixels are 24-bit RGB (or 32-bit RGBA) components:
class PrimitivePixel { int value; }; class PrimitivePixels { int[] value; }; class Pixel extends Integer {}; class Pixels { Pixel[] pixel; };,Pixelsstores a simple list ofPixels.Most languages will use 32-bit integers for bitmaps, but
javahas java.awt.Color for thus:class Pixel extends Color {};. Pros ofColor: is standard, popular structure which stores RGBA values, with functions to access individual channels, plus does not require more overhead thenIntegerto implement.Some architectures use other structures such as Y’UV (to improve performance for specific tasks).
Bitmaps are 2-dimensional tensors of pixels (most are supposed to show as sprites or as simple static images):
class Bitmap { Pixel[xResolution][yResolution] pixel; }; class Bitmaps { Bitmap[] bitmap; };,Bitmapsstores a simple list ofBitmaps with similar resolutions.Sprites are maps of directions to bitmaps which are used for simple 2-dimensional sims:
enum Direction { North, South, East, West; }; class Sprite { Bitmap[Direction.values().length] avatar; }; class Sprites { Sprite[] sprite; };,Spritesstores a simple list ofSprites, which require geometric translation (vertical bounces) for motion. Some sims use.gifs (as opposed toBitmaps) for more natural motion.Textures are bitmaps which are supposed to transform onto (do complex geometric convolutions onto) meshes:
class Texture extends Bitmap {}; class Textures { Texture[] tex; };,Texturesstores a simple list ofTextures with similar resolutions.
Coordinates are offsets from the origin of Euclidean spaces:
class PrimitiveCoordinate { double coord; }; class PrimitiveCoordinates { double[] coords; }; class Coordinate extends Double {}; class Coordinates { Coordinate[] coords; };,Coordinatessstores a simple list ofCoordinate.Positions are N-coordinates, for N-dimensional spaces:
class Position { Coord[dim] coord; }; class Positions { Position[] pos; };,Positionsstores a simple list ofPositions. package susuwu.{Immutable,}Pos{2,} improves performance for popular position formulas. Most of thus also have uses for vertices.Voxels [2] are
Positions which represent cubes (or spheres) plusColor, for N-dimensional spaces:class Voxel { Position pos; Pixel pixel; }; class Voxels { Voxel[] voxel; double lengthOrRadius; };Voxelsstores a simple list ofVoxels. Old-fashioned sims useVoxels.Vertices are positions of corners of ngons (triangles are the simplest ngons):
class Vertex extends Position {}; class Vertices { Vertex[] vertex; };,Verticesstores a simple list ofVertexes. New sims replaceVoxels(withVerticesplusTextures).Adjacent vertices are vertices attached with edges which form skeletons (or produce Ngons):
class AdjacentVertices { Map<Index, Indices> map; /*Indexes ofVertexes */ };is a simple “adjacency list“.
Surfaces are the flat geometric faces of
Meshes:class Surface { Vertices vertices; }; class Surfaces { Surface[] surfaces; };,Surfacesstores a simple list ofSurfaces, renderers setTextures onto thusSurfaces (or onto Ngons).Ngons are the smallest groups of adjacent vertices which can produce faces, for N-dimensional spaces:
class Ngon { Vertex[dim] vertex; }; class Ngons { Ngon[] ngons; };,Ngonsstores a simple list ofNgons.
Skeletons are lists of vertices whose “joints” (connections) allow angular motions:
class Skeleton { Vertex[] vertices; AdjacentVertices connections; }; class Skeletons { Skeleton[] skeleton; };,Skeletonsstores a simple list ofSkeletons.Quarternions are trigonometric structures used for motion of joints in
Skeletons.
Point clouds are lists of
Positions of corners of processed surfaces:class PointCloud { Positions point; }; class PointClouds { PointCloud[] pointClouds; };,PointCloudsstores a simple list ofPointClouds. MiDaS (monocular depth estimation) processes individualBitmaps into partial (just includes the surface)PointClouds (but is not accurate). cv2.calcOpticalFlowFarneback allows to produce whole (volumetric)Pointclouds, but requires motion images (lists of connectedBitmaps).Textured point clouds are
PointCloudwhich storeVoxels (not justPositions):class TexturedPointCloud { Voxels voxel; double voxelDiameter; }; TexturedPointCloud[] texturedPointClouds;,TexturedPointCloudsstores a simple list ofTexturedPointClouds. Different formulas are used to produce thus, since thepoints are not infinitisimal corners of surfaces but are spheres (which all have similar circumfurence but differentColors)Depth imagers (such as Microsoft Kinect 2) can produce whole connected point clouds (
PointClouds which includeEdges for adjacentPositions, which improves conversion into meshes):class ConnectedPointCloud { List<Position> point; Edges edges }; ConnectedPointCloud[] connectedPointClouds;stores a simple list ofConnectedPointClouds.
Meshes are graphs of vertices:
class Mesh { Vertex[] vertices; AdjacentVertices adjacentVertices; }; class Meshes { Mesh[] mesh; };,Meshesstores a simple list ofMeshes (assumesvertices.size() > Index.intValue()).Motions are sequences of motion vectors (of derivatives of position), which are produced for specific meshes (”skeletons”) to move: one popular form of motions is
.fbxFilmbox motions.Avatars (also known as models) are
Meshes plusTexturees plusSkeletons:class Avatar { Skeleton skeleton; Mesh mesh; Texture texture; }; class Avatars { Avatar[] avatars; };,Avatarsstores a simple list ofAvatars.Old-fashioned avatars use
Voxels(notMeshesplusTextures):class OldFashionedAvatar { Skeleton skeleton; Voxels voxels; }; class OldFashionedAvatar { OldFashionedAvatar[] oldFashionedAvatar; };,OldFashionedAvatarsstores a simple list ofOldFashionedAvatars. Old-fashioned avatars are just used in old sims (such as DirectX 6 sims) or raytracers.
Renderers load
Avatars, processMeshes into surfaces attached toSkeletons, plus load “skins” (textures) onto thus surfaces.Most popular renderers also use motions so
Skeletons move, but some renderers just produce static images.
tensorflow loads examples of
{input, output}(which is{question, answer}) to produce connectomes (groups of synapses) which process future questions into new answers.Descriptions are
char[]s orStrings, which store human-language versions of structures (such as ofMeshes). Descriptions are used to document structures (for humans, or for tools such astensorflow).
Separate variables versus dim lists
Pros of double xPos, yPos;:
If stuck with source-code compilers (or script interpreters) which can not optimize static lists into constant access, this improves RAM plus CPU use.
If stuck with those which can not unroll loops (loops such as dimension-agnostic physics functions), this improves CPU use.
Pros of double[] pos;:
Simple to switch to more (or less) dimensions: if loops are used for the physics functions (such as
package susuwudoes), allows to reuse dimension-agnostic source code.New compilers reduce those indirect accesses (the implicit pointer indirection) into constant accesses (hardcoded addresses).
double versus long
Some languages have long double (which gives nanometer resolution for all planets in our solar system), but:
long doubleuses the FPU, so can not use vector improvements (such as SSE2, nor GPGPU).long doubleuses 80 bits (longordoubleuse 64 bits).long doubleuses 80 bits (longordoubleuse 64 bits).
Pros of double[] position;:
doublecan store values in the range [(2-252)·21023, 21074] (inclusive), store NaN, or store Inf (although resolution is poor with large magnitudes, plus some functions can not use Double.NaN or Double.POSITIVE_INFINITY values).Since
double[]includes variable exponents,double[] position;:Is simple to use with formulas which involve squares (Math.pow()) or square roots (Math.sqrt()), such as Euclidean distance formulas (code which generalizes Math.hypot() to use more dimensions), which are common to physics sims.
double[] position = {0, 0, 0}with Sol (our sun)’s center-of-mass as the origin, can store positions as far as Pluto (but just with millimeter resolution).
Pros of long[] position;:
longcan store values in the range [-263, 263-1] (inclusive) (with similar resolution for all of those values).Since
long[]has fixed resolution:The hardware (CPUs, GPGPUs, TPUs) plus software (the physics sim functions) is more simple.
All systems allow
longbitshift (<<=,>>=) use (withjava,doubledoes not allow<<=nor>>=, but requires intrinsic functions such as Math.scalb). Versus division (also versus multiplication), bitshifts improve CPU use.All positions have similar resolution (instead of femtometer resolution for Sol‘s core (millimeter resolution for Pluto‘s surface), the whole planet can have nanometer resolution).
long[]stores sufficient quantized positions to give nanometer resolution for the whole surface of the largest planet in our solar system (but with this resolution, the distance is limited to a single planet, such as Jupiter, with the planet’s center-of-mass as origin).
volatile versus synchronized versus Atomic* versus
Those are the most popular structures to ensure that multiple Threads (or other units of execution such as java.util.concurrent.ExecutorService) follow the spatiotemporal structure of the source code. More “explicit“ (granular) structures exist, which do not introduce as much blocks (as much “pauses“), but which are more difficult to use.
synchronized
If interpretation of so is true (am new to java, so asked Solar-Pro-2, which says is true), https://johanley.github.io/java-practices-snapshot/topic/TopicAction5ade.html?Id=35 says:
class Foo { synchronized anInstanceFunction( { ... } })causes suchsynchronizedinstance functions ofclass Footo use an exclusive “instance lock” (not possible to choose which functions use which locks, exceptstaticversus notstatic).class Foo { synchronized static someStaticFunction( { ... } })causes suchsynchronizedstatic functions ofclass Footo use an exclusive “static lock” (not possible to choose which functions use which locks, exceptstaticversus notstatic).synchronized(this)causes the current execution to block (to pause) unti no contexts of execution use the instance lock, irrespective of if the current function is set tosynchronized.synchronized(this.getClass())(which does whatsynchronized(Foo.class)does, ifthis.getClass() == Foo) causes the current execution to block (to pause) unti no contexts of execution use the static lock, irrespective of if the current function is set tosynchronized.
java.util.concurrent.locks.ReentrantLock
If interpretation of this discussion with Solar-Pro-2 is true:
java.util.concurrent.locks.ReentrantLock is the granular version of
synchronized.The code must setup (plus store, plus acquire, plus release) separate
ReentrantLockinstances with explicit instructions, thus more difficult to use.Due to separate
ReentrantLockinstances, numerous units of execution can use 1this, if those units of execution contest for separateReentrantLocks. Thus more units of execution (versussynchronized, which limits allsynchronizedblocks ofthisto 1 unit of execution).
volatile primitive or AtomicPrimitive
If interpretation of so is true, this discussion with Solar-Pro-2 says:
both ensure that
javafollows the explicit temporal precedence of the source code (withoutvolatileorAtomic,javaoptimizes the execution such that race conditions are possible in executables which use multiple executors (such as thread pools or SMP).both ensure that stores whose size is more than the CPU architecture’s word size (thus, 64-bit primitives such as
doubleorlong) are stored whole. With novolatileorAtomicsuffix, stores from one unit of execution can cause separate contexts of execution which access those addresses to process spliced versions (”tearing“: versions whose first 32-bits stores the new value but whose second 32-bits stores the old value).Just
AtomicPrimitivesuits 64-bit CAS (volatile long foo = 42; if(42 < ++foo)is undefined,AtomicLong foo = 42; if(42 < foo.incrementAndGet())is true).
Solar-Pro-2 says this is true, plus suggests the alternative java.util.concurrent.atomic.
Synopsis
This simple java fish sim still has much code churn around which position data structures to use.
./posts/GoogleStore_MicrosoftStore_Ubuntu_scripted_tool_sims.md lists numerous physics sims for school use.
Most search results for such discussions just show physics engines’ documents of float versus double:

