[Preview] Simple fish sim for Java, which will include reusable public classs (for new sims to use)
[Version of this post is SusuJava@0854137. The GitHub pos2_PosBounds branch has the most new version.]
Discussion
./posts/FishSim.md is split from ../SusuPosts/posts/Human_ancestors_are_fish.md#request-java-fish.
The build script moved to ./susuwu/build.sh. Usage:
./susuwu/build.shThe source code moved to ./susuwu/FishSim.java (
package susuwu.FishSim;).The original version of this source code was produced through Solar-Pro-2, but the goal is just to use thus as a template (for future versions to replace all with own source code).
Uses ./susuwu/SimUsages.java:
class SimUsagesshowsFpsTextModestatistics such asfpsorms. Requirements: some render loop (for measurements). Is not specific to the renderer used. Was produced forclass FishSim, so the text (plus comments) assume the organisms areclass Fish, butSimUsagesis not specific toclass Fish.Uses ./susuwu/Calculus.java:
public class Calculus { /* `class Calculus` houses simple trigonometric (transcendental) `public static` functions. Future versions will include true calculus functions (such as "area-under-curve" integrals, or "False Position" or "Quadratic Interpolation" root formulas). */Will use ./susuwu/ImmutablePos.java:
public class ImmutablePos implements java.lang.Cloneable, java.util.RandomAccessstores constant vectors (first-order tensors), to future-proof (for volumetrics). Usage:double acceptsConsts(ImmutablePos pos)../susuwu/Pos.java:
public class Pos extends ImmutablePosstores mutable vectors (first-order tensors). Usage:void setsPos(Pos pos)../susuwu/ImmutablePos2.java:
public class ImmutablePos2 extends ImmutablePosis the 2-dimensional specialization ofclass ImmutablePos. Usage:double acceptsConsts(ImmutablePos2 pos2)../susuwu/Pos2.java:
public class Pos2 extends Posis the 2-dimensional specialization ofclass Pos. Usage:Pos2 position;.
Uses ./susuwu/Forces.java:
public class Forces implements java.lang.Cloneable“/* Usage:import susuwu.Forces;... replacesdouble fooDistance; double fooFactor;withForces fooForces;, so otherdoubles are not confused with those. */”Forces.posIfDistSum(posDes, posSource, dist): for Boids groups:if(posIfDistPow2Sum(averagePosOfGroup, posOfIndividual, distPow2ToIndividual) { ++sizeOfGroup; }. ForFish::apply*(), reduces duplicate code.Forces.dposScaleSum(dposDes, d2pos, dposSource): for Boids groups:if(dposScaleSum(derivativeOfPosition, secondDerivOfPos, averageDposOfGroup)) { position += derivativeOfPosition; }, reduces duplicate code.
Uses ./susuwu/ImmutablePosBounds.java:
public class ImmutablePosBounds implements java.lang.Cloneable, usage:ImmutablePosBounds posBounds(PosBoundsMode);enum ImmutablePosBounds: stores how sims enforce bounds.if
PosBoundsMode.wrapAroundResolution, withboundsResolutionFactor = 2the view is close to a natural ocean.
ImmutablePosBounds::getBounds(): to replaceFishSim::resolutionfor physics uses. Introducedboundsfor this (to allow out-of-view positions). Notice: for simple sims, this canreturn resolutionf;.setBounds({resolution[0] * 2, resolution[1] * 2};boolean isPosInBounds(double[] pos): replaces duplicate code which tests for ifposis in bounds. Allows 2-dimensions or volumetric.boolean posBound(double[] pos, ImmutablePosBounds posBounds): enforces bounds ontopos(Fish::setPos(newPos)uses this). IfPosBoundsMode.boundless, just testspos.public double[] posDiff(double[] pos, double[] o): reduces duplicate code for complex (such asPosBoundsMode.wrapAroundResolution) distances.Fish::getPosDiff(Fish o): usesFishSim::posBounds.posDiffso distances followPosBoundsMode.wrapAroundResolution.
./susuwu/PosBounds.java:
public class PosBounds extend ImmutablePosBoundsis the mutable (public void set) version ofImmutablePosBounds.
The original version of this source code was produced through Solar-Pro-2, but the goal is just to use thus as a template (for future versions to replace all with own source code).
Prefixes (used for variables / functions / classes): +`Class` introduces Class, -`Class` removes Class, @`Class` changes (neutral or improves) Class (as used for git commit messages). :%s/from/to/ shows vim regular expressions.
Notice: this git branch improves Solar-Pro-2‘s original source code as this list (plus GitHub‘s /compare/ tool shows) shows:
:%s/, 0, 0, 0/, 0, 0/: fixes “error: method rotate in class Transform cannot be applied to given types; ... actual and formal argument lists differ in length”+
frameCount, +lastTime, +fps, +fpsTextto show FPS (produced through Solar-Pro-2).@
AnimationTimer::handle(): show true FPS, plus do not to redrawfpsTextunlessfpschanges.
@
Fish::applySeparation(): reuse values.@
Fish::applyWallAvoidance(): reuse values, plus replace magic constants withBOUNDS_DISTANCE.@
Fish::update(): if rotation is miniscule, this reuses transforms (to improvefps, butfpsis too unstable to notice differences.)+
*_FACTOR: (= 1for original results), scalesFish::apply*()forces.@
SEPARATION_FACTOR: (from1) to2, so schools are loose enough to view individual fish.@
SEPARATION_DISTANCE: (from50) to22, so fish still school.
@
resolution: (from {800, 600}) to {1280, 720}, since most computers (plus smartphones) can show 720p resolution.@
fishCount: (from50) to102, since the window now has more room.
@
Fish::createFishShape(): produce 2 colors of fish.@
class FishSim: is now close to a fluid particle sim which has 2 types of molecules which group to similar molecules (such as oleophilic compounds) plus separate from nonsimilar molecules (such as oleophobic compounds), except the numerous steps of Boids formula cause some emergent phenomenon which simple molecules do not possess.
Notice: Used Solar-Pro-2 to improve codeflow (of the ancestor git commit --- which was half (1 / 2) human-produced source code --- to thus) so fps improves:
@
class FishSim: moveclass Fish-specific values into @class Fish.@
class FishSim: usejava.util.concurrent.Executor{s,Service}to offloadupdateFish()physics (now uses 2 CPUs).+
gridResolution, @updateFish(): usegridResolutionto splitList<Fish> fishListintoList<Fish>[][] grid(which reduces O(n^2) to O(n^2 / (resolution[] / gridResolution))) CPU use)).-
javafx.scene.shape.Polygon, +javafx.scene.canvas.Canvas, +javafx.scene.canvas.GraphicsContext: improves renderer CPU use?@
class FishSim: replaces1.0 / 2 < random.nextDouble()withrandom.nextBoolean().{-
Fish::createFishShape(), -Fish::getShape()}, {+Fish::render(), +FishSim::renderFish()}: switch toGraphicsContext.@
Fish::update:Fishnow wrap around (to opposite edges) if out-of-bounds.Notice: the list which follows is all own improvements (versus version above). Own version:
@
FishSim::*, @Fish::*: now mutable (since future versions will allow to resize windows plus configure distances). TODO: introduceget*()methods (so that typos do not reconfigure constants, such as view distances).@
gridResolution: documents minimum value which enforces*_DISTANCEs.@
applyFlockingRules(): replaces magic constants (100) withgridResolution(fixes undefined behaviour ifgridResolutionchanges).@
Fish::applyFlockingRules(), @Fish::update(): Replaces magic constants ({2600,1600}) with {resolution[0],resolution[1]}.@
class FishSim: reducespositionInterval(from5) to2(sinceExecutorServiceis used, this does not lowerfps) so physics is smooth.+
Fish::isSimilarTo(), +isSimilarTolerance: limits schools to similarFish. @apply*(): uses thus.+
boolean redFishAreAggressiveOrPoisonous: changes howisSimilarTo(Fish other)usescolor.getRed()@
FishSim::start(): produces all possible colors ofFish.Fishschools are now more complex than fluid particles.
+
FishSim::refreshLoop(): housesFishSim::ApplicationTimer::handle()‘s codeflow. Reason: so is simple for future versions to switchnew AnimationTimer() {@Override public void handle(long now) { refreshLoop(); }}.start();to alternatives (such as toTimeline timeline = new Timeline(new KeyFrame(Duration.millis(1000.0 / monitorRefreshHertz), event -> { refreshLoop(); })); timeline.setCycleCount(Animation.INDEFINITE); timeline.play();).@
FishSim::fpsText:String.format("%4.2f", fps)(4.sofpsText.size()does not change iffpsmagnitude does,.2to show miniscule differences).@
FishSim::fpsText: show milliseconds used per monitor refresh (”draw ms”), plus perupdateFish()(”physics ms”), plus showfishList.size(), plusfishShown.
+
FishSim::fpsTextRefresh(): houses theFishSim::fpsTextcodeflow, whichrefreshLoop()uses.+
FishSim::FpsTextMode(): says which resources forfpsTextto show.fpsTextRefresh()uses this.
@
FishSim::*: replaces pairs of 2ints withint[2](replaced 2doubles withdouble[2]), to future-proof (forclass Pos2). Such as: -WIDTH, -HEIGHT, +resolution[].+
double[] resolutionf = {resolution[0], resolution[1]};: for physics code which requiresdouble[].+
FishSim::outOfBounds(): improves @FishSim::updateFish()(which now uses this ifFishnot ingridbounds).+
void Fish::setPos(double[] newPos): ifFishnot in bounds, usesFishSim::outOfBounds().
@
FishSim::updateFish(): replaces magic constants (resolution[] / gridResolution) withgrid.length, to ensure correct access if the code which producesgridchanges.@
FishSim::updateFish(): produces extragrids ifresolution[]is not a multiple ofgridResolution, so thatFishwith position close to the resolution (close to edges / bounds) are still included.@
FishSim::updateFish(): moves bounds test intoFishSim::posBounds.posBound(), whichFish::setPos()uses.@
class FishSim: +fishVolume, +fishLengthsSep,fishPerVolume: sofishCountscales to resolution.@
FishSim::renderFish():if(posBounds.isPosInBounds(fish.pos))reduces calls tofish.render()(improvesfpsfor sims with huge unshown groups of fish).@
class Fish: +boolean isInBounds;storesboolean posBounds.posBound()‘sreturnvalue (improves CPU use). TODO: rename toisVisible?@
FishSim::updateFish():if(fish.isInBounds) {}aroundgrid[gridPos[0]][gridPos[1]].add(fish);, soFishSimallows out-of-boundsFish.
@
class Fish: +boolean isVisible: improvesFishSim::renderFish()(reduces calls tofish.render(), which improvesfpsfor sims with huge unshown groups of fish).
+
boolean FishSim::setResolution(newResolution): this sets all variables (plus uses all functions) required forclass FishSimto switch tonewResolution. Blocks unless has exclusive access toReentrantLock updateFishLock, renderFishLock;.+
ReentrantLock updateFishLock: @updateFish()blocks unless has exclusive access to this.+
ReentrantLock renderFishLock: @renderFish()blocks unless has exclusive access to this.
Notice: replaced most of Solar-Pro-2‘s original FishSim.java, as this intro documents (plus GitHub‘s /compare/ tool shows).
How to improve
One obvious submodule to introduce is “predator / prey” dynamics, but for now have chosen not to introduce thus, due to ethical concerns such as neutral monism.
Improve CPU use (do not know how to, have chose not to use OOP
classs for most of the physics due to concerns for the CPU use).FishSimis physics-bound, guess offload to GPGPU can improve this?Since
updateFish()‘s formulas use much CPU, choose how to show smooth motion with24 > physicsFps.@
updateFish(): movefish.pos += fish.dposintorefreshLoop(), as ? Or into a separate +physicsLoop()?Replace
fish.pos += fish.dposwithfish.pos += fish.dpos * factorto ensure motion is continuous.factor = physicsRefreshHertz / fpsorfactor = physicsRefreshHertz / physicsFps?
Improve the user interface (now is just
fpsTextRefresh(), which shows resource use, plusrenderFish()‘s viewport which shows the models ofclass Fish), to allow to scroll the viewport around (requiresresolutionto include 2 moredoubles, to store viewport offset, whichfish.isVisiblemust use), plus allow to set numerous options (which for now have “functional constness” so those invariants allowjavato use more efficient execution), plus allow the user to control one of the organisms (which for now are justFish).FishSim‘s renderer (renderFish) is separate fromFishSim‘s physics loop (updateFish) (PhysicsMode physicsModeallows to execute on separate CPUs throughAnimationTimer,Timeline, orExecutorService), but still shares the process with the physics loop.Can still execute
updateFish()as part ofrenderFish()ifphysicsMode = PhysicsMode.synchronous*, but future versions are supposed to drop synchronous modes.Should split
updateFish()into a new executable (such as +./susuwu/PhysicsLoop.java) which produces (plus moves) instances ofclass Fish, withFishpositions sent to +./susuwu/GraphicsShow.java(which showsFishat those positions), to allow servers forFishSim.updateFish()is difficult to do at 24fps (the minimum which shows smooth motion to humans) with thousands ofFish. IfFishSim‘smonitorRefreshHertzis >physicsRefreshHertz(in particular, if24 > physicsRefreshHertz): sumpos += dposon monitor refresh (or some sort of simple motion loop), so movement is smooth.
Introduce servers for shared experiences. But until
FishSimhas interfaces for user interactions, servers are limited to RO./susuwu/PhysicsLoop.javahosts with passive users. Due to variable latencies to / from servers, what those./susuwu/PhysicsLoop.javaservers must do:PhysicsMode physicsMode=PhysicsMode.separateFps.Send the derivative of position (
dpos), so that (even in the presence of server congestion) each monitor refresh continues to show smooth movement ofFishs.Send the absolute positions (
pos), so that position drifts (due to server congestion, or due to insufficient monitor refresh to follow the physics loop) are set back to shared, true position values.
Introduce continuous goals for
class Fish(with no predator / prey dynamics, though, goals are limited), whose progress is stored.Introduce new organisms (for now
class FishSimis limited toclass Fish). Do not know specifics, but guess more numerous organisms improves the total list of goals forFishSimorganisms to pursue../posts/MultiuserConcernsPlusGoals.md discusses numerous goals to pursue.
Introduce volumetrics (for now the renderer is 2-dimensional, but most of
FishSimwas produced as “dimension-agnostic” (future-proof) source code (on thepos2branch version ofFishSim.java), so guess is simple to do (but new tojava, so do not know which libs to use). How to introduce pseudo-volumetric visuals with the current (javafx, 2-dimensional) canvas:If “depth” is introduced (if {
Fish.pos,Fish.dpos} include distance to viewport),Fish::render()must introduce occlusion (must hide sections of distantFishwhich overlap withFishwhich are close to viewport).If
Fish::render()uses distance to viewport to scale (geometric resize) theFishimages (which for now simple geometric primitives), the result is pseudo-volumetric models.If
Fish::render()uses distance to viewport to “scale” (geometric translation) theFishposition (Fish.pos) within the frustum, the result is parallax perspective.If
class FishSimallows to “tilt” (trapezoidal distortion) the viewport perspective, with the other improvements of this list (occlusion, distance scales, parallax perspectives), the result is similar to “2.5 dimensional” (such as Starcraft: Brood Wars) visuals.
Synopsis
Q: “Produce a C++
class fishwhich does a fish sim”. Grok-2‘s C++ fish simQ: “Use GLFW+Vulkan to produce virtual fish which swim around”. Solar-Pro-2‘s GLFW + Vulkan fish sim source code
Q: “Produce OpenGLES2 code which moves the sprites of some fish around.” Grok-2‘s’s OpenGLES2 fish sim source code.
Source code
./susuwu/build.sh
#!/bin/sh
#
# /* This is the new build script for `./susuwu/FishSim.java`. Notice: must execute this from `./susuwu/`. */
# /* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
# * <https://github.com/SwuduSusuwu/SusuJava/> has the newest version of `./susuwu/build.sh` (henceforth "*this source code*").
# * If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
# * If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those. */
PATH_TO_FX="${PATH_TO_FX:-"/usr/share/openjfx/lib/"}" # /* Notice: prefix " "s in path with slashes, such as "\ ". */
PATH_TO_CLASS="FishSim"
PATH_TO_SOURCE="${PATH_TO_CLASS}.java"
JAVA_MODULES="${JAVA_MODULES} --module-path ${PATH_TO_FX} --add-modules javafx.controls,javafx.fxml" # /* Notice: quotes around `${PATH_TO_FX}` give errors, so ensure to escape the path in `PATH_TO_FX=...` */
#JAVA_MODULES="${JAVA_MODULES} --module-path \"${PATH_TO_FX}\" --add-modules javafx.controls,javafx.fxml" # /* Notice: those quotes (around `${PATH_TO_FX}`) give "error: module not found: javafx.{fxml, controls}", so this version is not used. */
JAVA_FLAGS="${JAVA_FLAGS} --enable-native-access=javafx.graphics"
#JAVA_FLAGS="${JAVA_FLAGS} -XX:+HeapDumpOnOutOfMemoryError " # /* Notice: if "Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated" then uncomment this to use `jhat java_pid*.hprof` */
JAVA_FLAGS="${JAVA_FLAGS} -enableassertions" # /* Notice: remove `-enableassertions` so performance improves */
export JAVA_BUILD_TEST_FLAGS="-verbose"
export JAVA_TEST_FLAGS="-verbose:module"
if command -v sudo >/dev/null; then
APTITUDE="sudo apt -y install "
else
APTITUDE="apt -y install " # /* Fixes "The program sudo is not installed." on platforms such as smartphones */
fi
if ! test -d "${PATH_TO_FX}"; then # /* TODO: search for this if default (**Ubuntu**'s) path is not found */
${APTITUDE} openjfx || ${APTITUDE} libopenjfx-java
if ! test -d "${PATH_TO_FX}"; then
echo "$0: '${PATH_TO_FX}' dir not found. Use \`${APTITUDE} install openjfx\`, then set '\${PATH_TO_FX}' to the actual libs."
fi
fi
command -v java >/dev/null || ${APTITUDE} openjdk-21-jdk-headless || ${APTITUDE} default-jdk-headless
if [ -n "${GITHUB_ACTIONS}" ]; then
#shellcheck disable=SC2086 # /* Quotes cause "Unrecognized option:" */
javac ${JAVA_BUILD_TEST_FLAGS} ${JAVA_MODULES} ${PATH_TO_SOURCE} # /* Gives "error: cannot find symbol\n...\n symbol: class Force" */
# java ${JAVA_TEST_FLAGS} ${JAVA_FLAGS} ${JAVA_MODULES} ${PATH_TO_CLASS} # /* Assumes `main()` will `return` (but, `FishSim.java`'s does not) */
# java ${JAVA_TEST_FLAGS} ${JAVA_FLAGS} --source 16 ${JAVA_MODULES} ${PATH_TO_SOURCE} # /* Assumes `main()` will `return` (but, `FishSim.java`'s does not) */
else
# javac ${JAVA_MODULES} ${PATH_TO_SOURCE} # /* Gives "error: cannot find symbol\n...\n symbol: class Force" */
# java ${JAVA_FLAGS} ${JAVA_MODULES} ${PATH_TO_CLASS}
#shellcheck disable=SC2086 # /* Quotes cause "Unrecognized option:" */
java ${JAVA_FLAGS} --source 16 ${JAVA_MODULES} ${PATH_TO_SOURCE} # /* `---source` is workaround for "error: cannot find symbol\n...\n symbol: {class Force,variable Utils}" */
fi
exit $? #Status required so [*CodeQL*](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql) passes../susuwu/Calculus.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/Calculus.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
/* `class Calculus` houses simple trigonometric (transcendental) `public static` functions. Future versions will include true calculus functions (such as "area-under-curve" integrals, or "False Position" or "Quadratic Interpolation" root formulas).
* Notice: passing `null` (or `{}`) to variadic functions is undefined, future versions could `return Double.NaN;` or `throw new IllegalArgumentException("double... = null");`
* `class Calculus` does not use generics since [`java` generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but `:%s/double/float/` in `vim` will produce the `float` version (`:%s/double/long/` produces the `long` version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some `assert`s follow, thus document which arguments to use with this (without `-enableassertions`, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
*/
package susuwu; /* Usage: `import susuwu.Calculus;` */
public class Calculus { /* Usage: `Calculus.function(arguments)` */
private Calculus() {} // Notice: instantiation of `class Calculus` has no uses (no members, plus all functions are `static`).
/* Simple trigonometric functions (with 1 source) which `return` 1 result value: */
public static double pow2(double value) { // Usage: for code which otherwise uses `Math.pow(temp, 2)` (or `indirectGet() * indirectGet()`) this improves CPU use. Versus `varX * varX`, `pow2(varX)` is just 1 token to replace to switch to `varW`, thus less bug-prone for future versions.
return value * value;
}
public static double volume(double... position) { // Usage: is vararg version of "Cartesian volume"
double volume_ = 1;
for(double posW : position) {
volume_ *= posW;
}
return volume_;
}
public static double average(double... position) { // Usage: is vararg version of "Arithmetic average"
return volume(position) / position.length;
}
public static double sum(double... position) { // Usage: `return`s sum of `position[dim]`
double sum_ = 0;
for(double posW : position) {
sum_ += pow2(posW);
}
return sum_;
}
public static double distancePow2(double... position) { // Usage: if absolute values are not used, replaces `if(threshold < hypotenus(position))` (which uses expensive square roots) with `if(pow2(threshold) < distancePow2(position))` (which does not) to improve CPU use.
double distancePow2_ = 0;
for(double posW : position) {
distancePow2_ += pow2(posW);
}
return distancePow2_;
}
public static double hypotenus(double... position) { // Usage: is vararg version of `java`'s `double Math.hypot(double, double)`;
return Math.sqrt(distancePow2(position));
}
};./susuwu/Forces.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/Forces.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; /* Usage: `import susuwu.Forces;` */
/**
* {@code class Forces} stores values for physics forces (so other {@code double}s are not confused with thus), plus includes a few functions for use with thus.
* {@code class Forces} does not use generics since [`java` generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but {@code :%s/double/float/} in {@code vim} will produce the {@code float} version ({@code :%s/double/long/} produces the {@code long} version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some {@code assert}s follow, thus document which arguments to use with this (without {@code -enableassertions}, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
* Usage: replaces {@code double fooDistance; double fooFactor;} with {@code Forces fooForces;}
*/
public class Forces implements java.lang.Cloneable {
/* Member variables & constructors:
* Notice: future versions will move `double distance, factor` into `class ImmutableForces`, with `class Forces extends ImmutableForces`. */
public double distance = 0.0; // Notice: future versions will use `protected double distance`. Usercode should access through `setDistance(double)` or `getDistance()`.
public double distancePow2 = Calculus.pow2(distance); /* Usage: stores the `pow(distance, 2)` value, for reuses */
public double factor = 0.0; // Notice: future versions will use `protected double factor`. Usercode should access through `setFactor(double)` or `getFactor()`.
public Forces(double distance, double factor) {
setFactor(factor);
setDistance(distance);
}
public Forces(Forces o) {
set(o);
}
/* Public setter functions: */
public void set(Forces o) {
setFactor(o.factor);
setDistance(o.distance);
}
public void setFactor(double factor) {
this.factor = factor;
}
public void setDistance(double distance) {
this.distance = distance;
this.distancePow2 = Calculus.pow2(distance); // Notice: if uncomment `double distancePow2`, then uncomment this
}
/* Public getter functions:
* Notice: future versions will move `get*()` into `class ImmutableForces`, with `class Forces extends ImmutableForces`. */
public double getFactor() { return this.factor; }
public double getDistance() { return this.distance; }
public double getDistancePow2() { /* Usage: `if(this.getDistancePow2() < Calculus.distancePow2(double...))` replaces `if(this.getDistance() < Calculus.hypotenus(double...))` */
return this.distancePow2; // Notice: if uncomment `double distancePow2`, then uncomment this
}
/* Comparison functions:
* Notice: future versions will move {`equals(o)`, `hashCode()`} into `class ImmutableForces`, with `class Forces extends ImmutableForces`. */
@Override
public boolean equals(Object o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); }` */
if(null == o || !(o instanceof Forces)) {
return false;
}
return equals((Forces)o);
}
public boolean equals(Forces o) { /* Usage: is `equals(Object o)` with low CPU use */
return o.distance == distance && o.factor == factor;
}
@Override
public int hashCode() { /* Usage: `if(hashCode() != hashCode(o)) { System.err.println("non-similar values"); }` */
double[] doubleView = { distance, factor };
return java.util.Arrays.hashCode(doubleView);
}
/* `ImmutableForces` functions, which accept `double[] dest, source` or `Pos dest, ImmutablePos source`:
* For now, this is just functions for Boids groups (future versions will include physics functions, such as {attraction of masses, {repulsion, attraction} of {opposite, similar} charges}).
*/
// Usage: for Boids groups: `if(posIfDistSum(averageDposOfGroup, dposOfIndividual, distanceToIndividual) { ++sizeOfGroup; }`
public boolean posIfDistScaleSum(double[] posDes, double[] posSource, double dist) {
dist = Math.max(dist, Double.MIN_NORMAL); // Notice: `MIN_VALUE` (as epsilon) gives rounding errors, so use minimum normal value
return posIfDistSum(posDes, new double[] {posSource[0] / dist, posSource[1] / dist}, dist);
}
public boolean posIfDistSum(double[] posDes, double[] posSource, double dist) {
if(dist < distance) {
posDes[0] += posSource[0];
posDes[1] += posSource[1];
return true;
}
return false;
}
public boolean posIfDistPow2Sum(double[] posDes, double[] posSource, double distPow2) {
if(distPow2 < getDistancePow2()) {
posDes[0] += posSource[0];
posDes[1] += posSource[1];
return true;
}
return false;
}
public boolean posIfDistScaleSum(Pos posDes, ImmutablePos posSource, double dist) {
dist = Math.max(dist, Double.MIN_NORMAL); // Notice: `MIN_VALUE` (as epsilon) gives rounding errors, so use minimum normal value
return posIfDistSum(posDes, posSource.slashScalar(dist), dist);
}
public boolean posIfDistSum(Pos posDes, ImmutablePos posSource, double dist) {
if(dist < distance) {
posDes.plusEquals(posSource);
return true;
}
return false;
}
public boolean posIfDistPow2Sum(Pos posDes, ImmutablePos posSource, double distPow2) {
if(distPow2 < getDistancePow2()) {
posDes.plusEquals(posSource);
return true;
}
return false;
}
// Usage: for Boids groups: `if(dposScaleSum(derivativeOfPosition, secondDerivOfPos, averageDposOfGroup)) { position += derivativeOfPosition; }`
public boolean dposScaleSum(double[] dposDes, double d2posDes, double[] dposSource) {
double d2posSource = Math.sqrt(Calculus.pow2(dposSource[0]) + Calculus.pow2(dposSource[1]));
if(d2posSource > 0) {
dposDes[0] += (dposSource[0] / d2posSource) * d2posDes * factor;
dposDes[1] += (dposSource[1] / d2posSource) * d2posDes * factor;
return true;
}
return false;
/*
// Is faster, if `Math.sqrt(double)` costs more than `double[] dposSourceMsb = { Math.signum(double), Math.signum(double) }`
double[] dposSourcePow2 = { Calculus.pow2(dposSource[0]), Calculus.pow2(dposSource[1]) };
double[] dposSourceMsb = { Math.signum(dposSource[0]), Math.signum(dposSource[1]) };
double d2posSourcePow2 = dposSourcePow2[0] + dposSourcePow2[1];
if(d2posSourcePow2 > 0) { // TODO: ensure this scales to the original (with `Math.sqrt(double)`) values
dposDes[0] += (dposSourceMsb[0] * dposSourcePow2[0] / d2posSourcePow2) * d2posDes * factor;
dposDes[1] += (dposSourceMsb[1] * dposSourcePow2[1] / d2posSourcePow2) * d2posDes * factor;
return true;
}
return false;
*/
}
public boolean dposScaleSum(Pos dposDes, double d2posDes, ImmutablePos dposSource) {
double d2posSource = dposSource.magnitude();
if(d2posSource > 0) {
double magnitude = (d2posDes * factor / d2posSource);
dposDes.plusEquals(dposSource.starScalar(magnitude));
return true;
}
return false;
}
};./susuwu/ImmutablePos.java:
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/ImmutablePos.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; // Usage: `import susuwu.ImmutablePos;`
import java.util.Arrays; // `Arrays.toString([])`
/**
* {@code class ImmutablePos} stores constant vectors (first-order tensors).
* {@code class ImmutablePos} does not use generics since [{@code java} generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but {@code :%s/double/float/} in {@code vim} will produce the {@code float} version ({@code :%s/double/long/} produces the {@code long} version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some {@code assert}s follow, thus document which arguments to use with this (without {@code -enableassertions}, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
* Notice: was unaware of <a href="https://docs.oracle.com/en/java/javase/21/docs/api/jdk.incubator.vector/jdk/incubator/vector/DoubleVector.html">DoubleVector</a> when produced this, so no {@code implements DoubleVector} for now.
* @var pos Stores tensor of position coordinates (for internal {@code package susuwu} uses). Other {@code package}s should use {@link #dims()}, {@link #at(int)}, {@link #set(int, double)}. Notice: {@code class ImmutablePos} was almost set to {@code abstract}, due to confusion over which default {@code pos.length} to use: future versions could use {@code pos = {}}, {@code pos = {0}} or {@code pos = {0, 0, 0}}
* Usage: {@code double acceptsConsts(ImmutablePos pos)}
*/
public class ImmutablePos implements java.lang.Cloneable, java.util.RandomAccess {
protected double[] pos = {0, 0};
/* Constructor functions: */
public ImmutablePos() {}
public ImmutablePos(double... o) { /* Usage: `double[] o; ImmutablePos pos = ImmutablePos(o);` conversion constructor, or `ImmutablePos pos = ImmutablePos(0, 0);` manual constructor */
pos = o.clone();
}
public ImmutablePos(ImmutablePos o) { /* Usage: as clone constructor. TODO: clone virtual function addresses. */
pos = o.pos.clone();
}
@Override
public Pos clone() { /* Usage: `ImmutablePos pos = o.clone(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert o.at(index) == pos.at(index); }` */
return new Pos(this);
}
public Pos zeros() { /* Usage: `ImmutablePos pos = o.zeros(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert 0 == pos.at(index); }` */
return new Pos();
}
public Pos ones() { /* Usage: `ImmutablePos pos = o.ones(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert 1 == pos.at(index); }` */
Pos pos = new Pos();
pos.fill_(1);
return pos;
}
/* Immutable `pos` access functions: */
@Override
public String toString() { /* Usage: `String serialized = "pos = " + pos.toString() + ";";` */
return Arrays.toString(pos);
}
public int dims() { /* Usage: `for(int index = ImmutablePos.dims(); index--; ) { sum += ImmutablePos.at(index); }` */
return pos.length; // Notice: `pos.length` is the deprecated optimization of `pos.size();`, for internal uses
}
public int size() { /* Usage: adapter for `dims()`, so `class ImmutablePos` is compatible with popular `java` `interface`s such as `DoubleArrayView` */
return dims();
}
public double at(int index) { /* Usage: source which `assert ImmutablePos.at(index) == ImmutablePos.pos[index];` allows. */
assert 0 <= index && pos.length > index;
return pos[index]; // Notice: `pos[index]` trusts `java` to enforce `Array` bounds, is for internal use.
}
public double get(int index) { /* Usage: adapter for `at()`, so `class ImmutablePos` is compatible with popular `java` `interface`s such as `DoubleArrayView` */
return at(index);
}
/* Comparison functions: */
@Override
public boolean equals(Object o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); }` */
if(null == o || !(o instanceof ImmutablePos)) {
return false;
}
ImmutablePos oPos = (ImmutablePos)o;
if(pos.length != oPos.pos.length) {
return false;
}
for(int dim = 0; dim < pos.length; ++dim) {
if(oPos.pos[dim] != pos[dim]) {
return false;
}
}
return true;
}
@Override
public int hashCode() { /* Usage: `if(hashCode() != hashCode(o)) { System.err.println("non-similar values"); }` */
return java.util.Arrays.hashCode(pos);
}
/* Immutable simple trigo functions: */
public double volume() { /* Usage: `double arithmeticProduct = ImmutablePos.volume();` */
/*
// Notice: uncomment this if `double[] pos;` is replaced with `double pos0, pos1, ...;`
double volume_ = 1;
for(for int dim = 0, end = dims(): end > dim; dim++) {
volume_ *= get(dim); // Notice: `get(dim)` is slower than direct access, but allows discontinuous storage
}
return volume_;
*/
return Calculus.volume(pos); // Notice: assumes continuous storage
}
public double magnitudePow2() { /* Is tensor version of `Calculus.pow2(double) + Calculus.pow2(double)`. Usage: `double distanceToOriginPow2 = ImmutablePos.magnitudePow2();` */
/*
// Notice: uncomment this if `double[] pos;` is replaced with `double pos0, pos1, ...;`
double distancePow2_ = 0;
for(for int dim = 0, end = dims(): end > dim; dim++) {
distancePow2 += Calculus.pow2(get(dim)); // Notice: `get(dim)` is slower than direct access, but allows discontinuous storage
}
return distancePow2_;
*/
return Calculus.distancePow2(pos); // Notice: assumes continuous storage
}
public double magnitude() { /* Is tensor version of `java`'s `double Math.hypot(double, double)`. Usage: `double distanceToOrigin = ImmutablePos.magnitude();` */
return Math.sqrt(magnitudePow2());
}
public double distanceToPow2(ImmutablePos o) { /* Usage: if absolute values are not used, replaces `if(threshold < distanceTo(position))` (which uses expensive `Math.sqrt`) with `if(pow2(threshold) < distanceToPow2(position))` (which does not) to improve CPU use. */
int end = pos.length;
assert end == o.pos.length;
double distancePow2 = 0;
for(int dim = 0; end > dim; ++dim) {
distancePow2 += Calculus.pow2(pos[dim] - o.pos[dim]);
}
return distancePow2;
}
public double distanceTo(ImmutablePos o) { /* `return`s Euclidean distance. Usage: `double distToO = ImmutablePos.distanceTo(o);` */
return Math.sqrt(distanceToPow2(o));
}
/* Immutable vector (simple tensor) functions which accept other tensors:
* Notice: the functions after this row all require `Pos clone()` for the generic versions, thus no reason exists not to `return` the `Pos` version (which allows implicit conversion to `ImmutablePos`) */
public Pos plus(double... o) { /* Usage: `Pos.plus(o)` is the tensor version of `(Pos + o)` */
Pos pos = clone();
pos.plusEquals(o);
return pos;
}
public Pos minus(double... o) { /* Usage: `Pos.minus(o)` is the tensor version of `(Pos - o)` */
Pos pos = clone();
pos.minusEquals(o);
return pos;
}
public Pos star(double... o) { /* Usage: `Pos.star(o)` is the tensor version of `(Pos * o)` */
Pos pos = clone();
pos.starEquals(o);
return pos;
}
public Pos slash(double... o) { /* Usage: `Pos.slash(o)` is the tensor version of `(Pos / o)` */
Pos pos = clone();
pos.slashEquals(o);
return pos;
}
public Pos modulo(double... o) { /* Usage: `Pos.modulo(o)` is the tensor version of `(Pos % o)` */
Pos pos = clone();
pos.moduloEquals(o);
return pos;
}
public Pos pow(double... o) { /* Usage: `Pos.pow(o)` is the tensor version of `Math.pow(Pos, o)` */
Pos pos = clone();
pos.powEquals(o);
return pos;
}
public Pos plus(ImmutablePos o) { /* Usage: `Pos.plus(o)` is the tensor version of `(Pos + o)` */
Pos pos = clone();
pos.plusEquals(o);
return pos;
}
public Pos minus(ImmutablePos o) { /* Usage: `Pos.minus(o)` is the tensor version of `(Pos - o)` */
Pos pos = clone();
pos.minusEquals(o);
return pos;
}
public Pos star(ImmutablePos o) { /* Usage: `Pos.star(o)` is the tensor version of `(Pos * o)` */
Pos pos = clone();
pos.starEquals(o);
return pos;
}
public Pos slash(ImmutablePos o) { /* Usage: `Pos.slash(o)` is the tensor version of `(Pos / o)` */
Pos pos = clone();
pos.slashEquals(o);
return pos;
}
public Pos modulo(ImmutablePos o) { /* Usage: `Pos.modulo(o)` is the tensor version of `(Pos % o)` */
Pos pos = clone();
pos.moduloEquals(o);
return pos;
}
public Pos pow(ImmutablePos o) { /* Usage: `Pos.pow(o)` is the tensor version of `Math.pow(Pos, o)` */
Pos pos = clone();
pos.powEquals(o);
return pos;
}
/* Immutable vector (simple tensor) functions which accept scalars:
* Notice: almost used `double s` (acronym for "scalar"), but all similar functions use `o` (acronym for "other") */
public Pos plusScalar(double o) { /* Usage: `plusScalar(o)` is the compressed version of `plus(new double[]{o, o})` */
Pos pos = clone();
pos.plusEqualsScalar(o);
return pos;
}
public Pos minusScalar(double o) { /* Usage: `minusScalar(o)` is the compressed version of `minus(new double[]{o, o})` */
Pos pos = clone();
pos.minusEqualsScalar(o);
return pos;
}
public Pos starScalar(double o) { /* Usage: `starScalar(o)` is the compressed version of `star(new double[]{o, o})` */
Pos pos = clone();
pos.starEqualsScalar(o);
return pos;
}
public Pos slashScalar(double o) { /* Usage: `slashScalar(o)` is the compressed version of `slash(new double[]{o, o})` */
Pos pos = clone();
pos.slashEqualsScalar(o);
return pos;
}
public Pos moduloScalar(double o) { /* Usage: `moduloScalar(o)` is the compressed version of `modulo(new double[]{o, o})` */
Pos pos = clone();
pos.moduloEqualsScalar(o);
return pos;
}
public Pos powScalar(double o) { /* Usage: `powScalar(o)` is the compressed version of `pow(new double[]{o, o})` */
Pos pos = clone();
pos.powEqualsScalar(o);
return pos;
}
};./susuwu/Pos.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/Pos.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; // Usage: `import susuwu.Pos;`
/**
* {@code class Pos} stores mutable vectors (first-order tensors).
* {@code class Pos} does not use generics since [{@code java} generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but {@code :%s/double/float/} in {@code vim} will produce the {@code float} version ({@code :%s/double/long/} produces the {@code long} version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some {@code assert}s follow, thus document which arguments to use with this (without {@code -enableassertions}, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
* Notice: was unaware of <a href="https://docs.oracle.com/en/java/javase/21/docs/api/jdk.incubator.vector/jdk/incubator/vector/DoubleVector.html">DoubleVector</a> when produced this, so no {@code implements DoubleVector} for now.
* @var pos Stores tensor of position coordinates (for internal {@code package susuwu} uses). Other {@code package}s should use {@link #dims()}, {@link #at(int)}, {@link #set(int, double)}
*/
public class Pos extends ImmutablePos { /* Usage: `void setsPos(Pos pos) { pos.set(...); }`. */
/* Constructor functions: */
public Pos() {} /* Usage: as default constructor */
public Pos(double... o) { /* Usage: `double[] o; ImmutablePos pos = Pos(o);` conversion constructor, or `ImmutablePos pos = Pos(0, 0);` manual constructor */
pos = o.clone();
}
public Pos(ImmutablePos o) { /* Usage: as conversion constructor. TODO: clone virtual function addresses. */
pos = o.pos.clone();
}
/* `pos` functions: */
public double[] getPos() { /* Usage: use as `Pos.pos`. Notice: future versions could replace `double[] pos;` with `double pos0, pos1, ...;`, so use `.at(index)` or `.set(index, newValue)`. */
return pos; /* Notice: this trusts `java` to enforce `Array` bounds */
}
public void set(int index, double newValue) { /* Usage: `Pos.set(index, newValue)`. */
assert pos.length > index;
pos[index] = newValue; // Notice: `pos[index]` trusts `java` to enforce `Array` bounds, is optimization for internal use.
}
/* Vector (tensor) functions which accept other tensors: */
public void equals_(double[] o) { /* Usage: `Pos.equals_(o)` is the tensor version of `Pos = o` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] = o[dim]; // Notice: `pos[index] = o` trusts `java` to enforce `Array` bounds, is optimization of `set(index, o)` for internal use.
}
}
public void plusEquals(double[] o) { /* Usage: `Pos.plusEquals(o)` is the tensor version of `Pos += o` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] += o[dim]; // Notice: `pos[index] += o` trusts `java` to enforce `Array` bounds, is optimization of `set(index, get(index) + o)` for internal use.
}
}
public void minusEquals(double[] o) { /* Usage: `Pos.minusEquals(o)` is the tensor version of `Pos -= o` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] -= o[dim]; // Notice: `pos[index] $op= o` trusts `java` to enforce `Array` bounds, is optimization of `set(index, get(index) $op o)` for internal use.
}
}
public void starEquals(double[] o) { /* Usage: `Pos.starEquals(o)` is the tensor version of `Pos *= o` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] *= o[dim];
}
}
public void slashEquals(double[] o) { /* Usage: `Pos.slashEquals(o)` is the tensor version of `Pos /= o` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] /= o[dim];
}
}
public void moduloEquals(double[] o) { /* Usage: `Pos.moduloEquals(o)` is the tensor version of `Pos %= o` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] %= o[dim];
}
}
public void powEquals(double[] o) { /* Usage: `Pos.powEquals(o)` is the tensor version of `Pos = Math.pow(Pos, o)` */
assert pos.length == o.length;
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] = Math.pow(pos[dim], o[dim]);
}
}
/* Vector (tensor) functions which accept other tensors: */
public void equals_(ImmutablePos o) { /* Usage: `Pos.equals_(o)` is the tensor version of `Pos = o` */
equals_(o.pos);
}
public void plusEquals(ImmutablePos o) { /* Usage: `Pos.plusEquals(o)` is the tensor version of `Pos += o` */
plusEquals(o.pos);
}
public void minusEquals(ImmutablePos o) { /* Usage: `Pos.minusEquals(o)` is the tensor version of `Pos -= o` */
minusEquals(o.pos);
}
public void starEquals(ImmutablePos o) { /* Usage: `Pos.starEquals(o)` is the tensor version of `Pos *= o` */
starEquals(o.pos);
}
public void slashEquals(ImmutablePos o) { /* Usage: `Pos.slashEquals(o)` is the tensor version of `Pos /= o` */
slashEquals(o.pos);
}
public void moduloEquals(ImmutablePos o) { /* Usage: `Pos.moduloEquals(o)` is the tensor version of `Pos %= o` */
moduloEquals(o.pos);
}
public void powEquals(ImmutablePos o) { /* Usage: `Pos.powEquals(o)` is the tensor version of `Pos Math.pow(Pos, o)` */
powEquals(o.pos);
}
/* Vector (tensor) functions which accept scalars:
* Notice: almost used `double s` (acronym for "scalar"), but all similar functions use `o` (acronym for "otherInstance") */
public void fill_(double o) { /* Usage: documents that `equalsScalar(o)` is analogous to the `fill_(o)` of popular tensor `class`s */
equalsScalar(o);
}
public void equalsScalar(double o) { /* Usage: `equalsScalar(o)` broadcasts the scalar `o` to all, similar to `for(long index = dims(); index--; ) { set(index, o); }` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] = o; // Notice: `pos[index] = o` trusts `java` to enforce `Array` bounds, is optimization of `set(index, o)` for internal use.
}
}
public void plusEqualsScalar(double o) { /* Usage: `plusEquals(new double[]{o, o})` compresses down to `plusEqualsScalar(o)` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] += o; // Notice: `pos[index] += o` trusts `java` to enforce `Array` bounds, is optimization of `set(index, get(index) + o)` for internal use.
}
}
public void minusEqualsScalar(double o) { /* Usage: `minusEquals(new double[]{o, o})` compresses down to `minusEqualsScalar(o)` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] -= o; // Notice: `pos[index] $op= o` trusts `java` to enforce `Array` bounds, is optimization of `set(index, get(index) $op o)` for internal use.
}
}
public void starEqualsScalar(double o) { /* Usage: `starEquals(new double[]{o, o})` compresses down to `starEqualsScalar(o)` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] *= o;
}
}
public void slashEqualsScalar(double o) { /* Usage: `slashEquals(new double[]{o, o})` compresses down to `slashEqualsScalar(o)` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] /= o;
}
}
public void moduloEqualsScalar(double o) { /* Usage: `moduloEquals(new double[]{o, o})` compresses down to `moduloEqualsScalar(o)` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] %= o;
}
}
public void powEqualsScalar(double o) { /* Usage: `powEquals(new double[]{o, o})` compresses down to `powEqualsScalar(o)` */
for(int dim = 0; dim < pos.length; ++dim) {
pos[dim] = Math.pow(pos[dim], o);
}
}
};./susuwu/ImmutablePos2.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/ImmutablePos2.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; // Usage: `import susuwu.ImmutablePos2;`
/**
* {@code class ImmutablePos2} is the 2-dimensional specialization of {@code class ImmutablePos}, which stores constant vectors (first-order tensors).
* {@code class ImmutablePos2} does not use generics since [{@code java} generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but {@code :%s/double/float/} in {@code vim} will produce the {@code float} version ({@code :%s/double/long/} produces the {@code long} version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some {@code assert}s follow, thus document which arguments to use with this (without {@code -enableassertions}, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
* Notice: was unaware of <a href="https://docs.oracle.com/en/java/javase/21/docs/api/jdk.incubator.vector/jdk/incubator/vector/DoubleVector.html">DoubleVector</a> when produced this, so no {@code implements DoubleVector} for now.
* Usage: {@code double acceptsConsts(ImmutablePos2 pos) { double coord = pos.at(index); }}
* @var pos Stores tensor of position coordinates (for internal {@code package susuwu} uses). Other {@code package}s should use: {@link #dims()}, {@link #at(int)}, {@link #set(int, double)}
*/
public class ImmutablePos2 extends ImmutablePos {
/* Constructor functions: */
public ImmutablePos2() {} /* Usage: as default constructor */
public ImmutablePos2(double pos0, double pos1) { /* Usage: as manual constructor */
pos[0] = pos0;
pos[1] = pos1;
}
public ImmutablePos2(double... o) { /* Usage: `double[] o; ImmutablePos2 pos = ImmutablePos2(o);` conversion constructor */
assert null != o;
assert 2 == o.length;
pos = o.clone();
}
public ImmutablePos2(ImmutablePos2 o) { /* Usage: as clone constructor */
assert null != o;
pos[0] = o.pos[0];
pos[1] = o.pos[1];
}
public ImmutablePos2(Pos2 o) { /* Usage: as conversion constructor. TODO: clone virtual function addresses. */
assert null != o;
pos[0] = o.pos[0];
pos[1] = o.pos[1];
}
public ImmutablePos2(ImmutablePos o) { /* Usage: as conversion constructor. TODO: clone virtual function addresses. */
assert null != o;
assert 2 == o.pos.length; // Notice: `2` is optimization of `dims()`, for internal uses
pos[0] = o.pos[0];
pos[1] = o.pos[1]; // Notice: assumes `o.dims() >= 2`
}
public ImmutablePos2(Pos o) { /* Usage: as conversion constructor. TODO: clone virtual function addresses. */ /* Notice: must use `interface` (or multiple inheritance) to allow implicit conversion of `Pos2` into `ImmutablePos` plus into `ImmutablePos2` */
assert null != o;
assert 2 == o.pos.length; // Notice: `2` is optimization of `dims()`, for internal uses
pos[0] = o.pos[0];
pos[1] = o.pos[1]; // Notice: assumes `o.dims() >= 2`
}
@Override
public Pos clone() { /* Usage: `ImmutablePos pos = o.clone(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert o.at(index) == pos.at(index); }` */
return new Pos2(this);
}
@Override
public Pos zeros() { /* Usage: `ImmutablePos pos = o.zeros(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert 0 == pos.at(index); }` */
return new Pos2();
}
@Override
public Pos ones() { /* Usage: `ImmutablePos pos = o.ones(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert 1 == pos.at(index); }` */
return new Pos2(1, 1);
}
/* Immutable `pos` access functions */
@Override
public int dims() {
return 2; // Notice: `2` is optimization of `dims()`, for internal uses
}
@Override
public double at(int index) { /* Usage: source which `assert ImmutablePos.at(index) == ImmutablePos.pos[index];` allows. */
assert 0 <= index && 2 > index; // Notice: `2` is optimization of `dims()`, for internal uses
return pos[index]; // Notice: this trusts `java` to enforce `Array` bounds
}
/* Comparison functions: */
@Override
public boolean equals(Object o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); } */
if(null == o || !(o instanceof ImmutablePos)) {
return false;
}
ImmutablePos oPos = (ImmutablePos)o;
return 2 == oPos.pos.length && // Notice: `2` is optimization of `dims()`, for internal uses
oPos.pos[0] == pos[0] && oPos.pos[1] == pos[1];
}
public boolean equals(ImmutablePos2 o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); } */
return null != o && o.pos[0] == pos[0] && o.pos[1] == pos[1];
}
public boolean equals(Pos2 o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); } */
return null != o && o.pos[0] == pos[0] && o.pos[1] == pos[1];
}
/* Immutable trigo functions: */
@Override
public double volume() { /* Usage: `double arithmeticProduct = volume();` */
return pos[0] * pos[1];
}
@Override
public double magnitudePow2() { /* Usage: `double euclideanDistanceToOriginPow2 = magnitudePow2();` */
double distancePow2 = 0;
distancePow2 += Calculus.pow2(pos[0]);
distancePow2 += Calculus.pow2(pos[1]);
return distancePow2;
}
@Override
public double magnitude() { /* Usage: `double euclideanDistanceToOrigin = magnitude();` */
return Math.sqrt(magnitudePow2());
}
public double distanceToPow2(ImmutablePos2 o) { /* Usage: if absolute values are not used, replaces `if(threshold < distanceTo(position))` (which uses expensive `Math.sqrt`) with `if(pow2(threshold) < distanceToPow2(position))` (which does not) to improve CPU use. */
assert null != o;
double distancePow2 = 0;
distancePow2 += Calculus.pow2(pos[0] - o.pos[0]);
distancePow2 += Calculus.pow2(pos[1] - o.pos[1]);
return distancePow2;
}
public double distanceTo(ImmutablePos2 o) { /* `return`s Euclidean distance. Usage: `double distToO = ImmutablePos.distanceTo(o);` */
return Math.sqrt(distanceToPow2(o));
}
@Override
public double distanceToPow2(ImmutablePos o) { /* Usage: if absolute values are not used, replaces `if(threshold < distanceTo(position))` (which uses expensive `Math.sqrt`) with `if(pow2(threshold) < distanceToPow2(position))` (which does not) to improve CPU use. */
assert null != o;
assert 2 == o.pos.length; // Notice: `2` is optimization of `dims()`, for internal uses
return distanceToPow2(o);
}
@Override
public double distanceTo(ImmutablePos o) { /* `return`s Euclidean distance. Usage: `double distToO = ImmutablePos.distanceTo(o);` */
return Math.sqrt(distanceToPow2(o));
}
};./susuwu/Pos2.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/Pos2.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; // Usage: `import susuwu.Pos2;`
/**
* {@code class Pos2} is the 2-dimensional specialization of {@code class Pos}, which stores constant vectors (first-order tensors).
* {@code class Pos2} does not use generics since [{@code java} generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but {@code :%s/double/float/} in {@code vim} will produce the {@code float} version ({@code :%s/double/long/} produces the {@code long} version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some {@code assert}s follow, thus document which arguments to use with this (without {@code -enableassertions}, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
* Notice: was unaware of <a href="https://docs.oracle.com/en/java/javase/21/docs/api/jdk.incubator.vector/jdk/incubator/vector/DoubleVector.html">DoubleVector</a> when produced this, so no {@code implements DoubleVector} for now.
* Usage: {@code Pos2 position = Pos2(coord[0], coord[1]);}
* @var pos Stores tensor of position coordinates (for internal {@code package susuwu} uses). Other {@code package}s should use: {@link #dims()}, {@link #at(int)}, {@link #set(int, double)}
*/
public class Pos2 extends Pos {
/* Constructor functions */
public Pos2() {} /* Usage: as default constructor */
public Pos2(double pos0, double pos1) { /* Usage: as manual constructor */
pos[0] = pos0;
pos[1] = pos1;
}
public Pos2(double... o) { /* Usage: `double[] o; Pos2 pos = Pos2(o);` conversion constructor */
assert null != o;
assert 2 == o.length; // Notice: `2` is the hardcoded value for `Pos2.dims()`, just for internal use.
pos = o.clone();
}
public Pos2(Pos2 o) { /* Usage: as clone constructor */
assert null != o;
pos[0] = o.pos[0];
pos[1] = o.pos[1];
}
public Pos2(ImmutablePos2 o) { /* Usage: as conversion constructor. */
assert null != o;
pos[0] = o.pos[0];
pos[1] = o.pos[1];
}
public Pos2(ImmutablePos o) { /* Usage: as conversion constructor. */
assert null != o;
assert 2 == o.pos.length; // Notice: `2` is the hardcoded value for `Pos2.dims()`
pos[0] = o.pos[0];
pos[1] = o.pos[1];
}
public Pos2(Pos o) { /* Usage: as conversion constructor. */
assert null != o;
assert 2 == o.pos.length; // Notice: `2` is the hardcoded value for `Pos2.dims()`
pos[0] = o.pos[0]; // Notice: is optimization of `set(0, o.at(0));` ..., just for internal use.
pos[1] = o.pos[1]; // Notice: assumes `o.dims() >= 2`
}
@Override
public Pos clone() { /* Usage: `ImmutablePos pos = o.clone(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert o.at(index) == pos.at(index); }` */
return new Pos2(pos[0], pos[1]);
}
@Override
public Pos zeros() { /* Usage: `ImmutablePos pos = o.zeros(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert 0 == pos.at(index); }` */
return new Pos2();
}
@Override
public Pos ones() { /* Usage: `ImmutablePos pos = o.ones(); assert o.dims() == pos.dims(); for(int index = pos.dims(); index--; ) { assert 1 == pos.at(index); }` */
return new Pos2(1, 1);
}
/* `pos` functions: */
@Override
public int dims() {
return 2; // Notice: is optimization of `return pos.length;`, plus allows future changes of `double[] pos;` to `double pos0, pos1;`
}
@Override
public double at(int index) { /* Usage: source which `assert ImmutablePos.at(index) == ImmutablePos.pos[index];` allows. */
assert 0 <= index && 2 > index; // Notice: `2` is hardcoded value of `Pos2.dims()`, just for internal use.
return pos[index]; // Notice: this trusts `java` to enforce `Array` bounds
}
@Override
public void set(int index, double newValue) { /* Usage: `Pos.set(index, newValue)`. */
assert 0 <= index && 2 > index; // Notice: `2` is hardcoded value of `Pos2.dims()`, just for internal use.
pos[index] = newValue; // Notice: this trusts `java` to enforce `Array` bounds
}
/* Comparison functions: */
@Override
public boolean equals(Object o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); }` */
if(null == o || !(o instanceof ImmutablePos)) {
return false;
}
ImmutablePos oPos = (ImmutablePos)o;
return 2 == oPos.pos.length && // Notice: `2 == oPos.pos.length` is optimization of `dims() == oPos.dims()`, for internal use
oPos.pos[0] == pos[0] && oPos.pos[1] == pos[1]; // Notice: `pos[dim]` is optimization of `at(dim)`, for internal use
}
public boolean equals(ImmutablePos2 o) { /* Usage: is `equals(Object o)` with low CPU use*/
return null != o && o.pos[0] == pos[0] && o.pos[1] == pos[1]; // Notice: `pos[dim]` is optimization of `at(dim)`, for internal use
}
public boolean equals(Pos2 o) { /* Usage: is `equals(Object o)` with low CPU use*/
return null != o && o.pos[0] == pos[0] && o.pos[1] == pos[1];
}
/* Immutable trigo functions: */
@Override
public double volume() { /* Usage: `double arithmeticProduct = volume();` */
return pos[0] * pos[1]; // Notice: `pos[0]` is optimization of `at(0)`, just for internal use.
}
@Override
public double magnitudePow2() { /* Usage: `double euclideanDistanceToOriginPow2 = magnitudePow2();` */
double distancePow2 = 0;
distancePow2 += Calculus.pow2(pos[0]); // Notice: `pos[0]` is optimization of `at(0)`, just for internal use.
distancePow2 += Calculus.pow2(pos[1]); // Notice: `pos[1]` is optimization of `at(1)`, just for internal use.
return distancePow2;
}
@Override
public double magnitude() { /* Usage: `double euclideanDistanceToOrigin = magnitude();` */
return Math.sqrt(magnitudePow2());
}
@Override
public double distanceToPow2(ImmutablePos o) { /* Usage: if absolute values are not used, replaces `if(threshold < distanceTo(position))` (which uses expensive `Math.sqrt`) with `if(pow2(threshold) < distanceToPow2(position))` (which does not) to improve CPU use. */
assert null != o;
assert 2 == o.pos.length; // Notice: is optimization of `assert dims() == o.dims();`, for internal use.
double distancePow2 = 0;
distancePow2 += Calculus.pow2(pos[0] - o.pos[0]); // Notice: is optimization of `at(0) - o.at(0)`
distancePow2 += Calculus.pow2(pos[1] - o.pos[1]); // Notice: is optimization of `at(1) - o.at(1)`
return distancePow2;
}
@Override
public double distanceTo(ImmutablePos o) { /* `return`s Euclidean distance. Usage: `double distToO = ImmutablePos.distanceTo(o);` */
return Math.sqrt(distanceToPow2(o));
}
/* Vector (tensor) functions with `double...`: */
@Override
public void plusEquals(double... o) {
assert null != o;
assert 2 == o.length; // Notice: `2` is the hardcoded value for `Pos2.dims()`, just for internal use.
pos[0] += o[0];
pos[1] += o[1];
}
@Override
public void minusEquals(double... o) {
assert null != o;
assert 2 == o.length;
pos[0] -= o[0];
pos[1] -= o[1];
}
@Override
public void starEquals(double... o) {
assert null != o;
assert 2 == o.length;
pos[0] *= o[0];
pos[1] *= o[1];
}
@Override
public void slashEquals(double... o) {
assert null != o;
assert 2 == o.length;
pos[0] /= o[0];
pos[1] /= o[1];
}
@Override
public void moduloEquals(double... o) {
assert null != o;
assert 2 == o.length;
pos[0] %= o[0];
pos[1] %= o[1];
}
@Override
public void powEquals(double... o) {
assert null != o;
assert 2 == o.length;
pos[0] = Math.pow(pos[0], o[0]);
pos[1] = Math.pow(pos[1], o[1]);
}
/* Vector (tensor) functions with `ImmutablePos`: */
@Override
public void plusEquals(ImmutablePos o) {
assert null != o;
assert 2 == o.pos.length; // Notice: `2` is the hardcoded value for `Pos2.dims()`
pos[0] += o.pos[0];
pos[1] += o.pos[1];
}
@Override
public void minusEquals(ImmutablePos o) {
assert null != o;
assert 2 == o.pos.length;
pos[0] -= o.pos[0];
pos[1] -= o.pos[1];
}
@Override
public void starEquals(ImmutablePos o) {
assert null != o;
assert 2 == o.pos.length;
pos[0] *= o.pos[0];
pos[1] *= o.pos[1];
}
@Override
public void slashEquals(ImmutablePos o) {
assert null != o;
assert 2 == o.pos.length;
pos[0] /= o.pos[0];
pos[1] /= o.pos[1];
}
@Override
public void moduloEquals(ImmutablePos o) {
assert null != o;
assert 2 == o.pos.length;
pos[0] %= o.pos[0];
pos[1] %= o.pos[1];
}
@Override
public void powEquals(ImmutablePos o) {
assert null != o;
assert 2 == o.pos.length;
pos[0] = Math.pow(pos[0], o.pos[0]);
pos[1] = Math.pow(pos[1], o.pos[1]);
}
/* Vector (tensor) functions with scalars:
* Notice: almost used `double s` (acronym for "scalar"), but all similar functions use `o` (acronym for "otherInstance") */
@Override
public void plusEqualsScalar(double o) { /* Usage: `plusEquals(new double[]{o, o})` compresses down to `plusEqualsScalar(o)` */
pos[0] += o;
pos[1] += o;
}
@Override
public void minusEqualsScalar(double o) { /* Usage: `minusEquals(new double[]{o, o})` compresses down to `minusEqualsScalar(o)` */
pos[0] -= o;
pos[1] -= o;
}
@Override
public void starEqualsScalar(double o) { /* Usage: `starEquals(new double[]{o, o})` compresses down to `starEqualsScalar(o)` */
pos[0] *= o;
pos[1] *= o;
}
@Override
public void slashEqualsScalar(double o) { /* Usage: `slashEquals(new double[]{o, o})` compresses down to `slashEqualsScalar(o)` */
pos[0] /= o;
pos[1] /= o;
}
@Override
public void moduloEqualsScalar(double o) { /* Usage: `moduloEquals(new double[]{o, o})` compresses down to `moduloEqualsScalar(o)` */
pos[0] %= o;
pos[1] %= o;
}
@Override
public void powEqualsScalar(double o) { /* Usage: `powEquals(new double[]{o, o})` compresses down to `powEqualsScalar(o)` */
pos[0] = Math.pow(pos[0], o);
pos[1] = Math.pow(pos[1], o);
}
};./susuwu/ImmutablePosBounds.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/ImmutablePosBounds.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; /* Usage: `import susuwu.ImmutablePosBounds;` */
import java.util.Arrays;
/**
* `class ImmutablePosBounds` says how sims enforce position bounds.
* Usage: `ImmutablePosBounds posBounds;`
* `class ImmutablePosBounds` does not use generics since [`java` generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but `:%s/double/float/` in `vim` will produce the `float` version (`:%s/double/long/` produces the `long` version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some `assert`s follow, thus document which arguments to use with this (without `-enableassertions`, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
*/
public class ImmutablePosBounds implements java.lang.Cloneable { /* Usage: replaces `double fooDistance; double fooFactor;` with `ImmutablePosBounds fooImmutablePosBounds;` */
/* Member variables & constructors: */
public enum PosBoundsMode { // `PosBoundsMode` says how the sim must do `pos[dim] += dpos[dim]` (derivatives of positions).
invalidArgumentException, // `if(!isPosInBounds(pos)) { throw new IllegalArgumentException(); }`
wrapAroundResolution, // `pos[dim] = (getBounds()[dim] + pos[dim] + dpos[dim]) % getBounds()[dim];`.
clampToResolution, // `pos[dim] = Math.max(0, Math.min(getBounds()[dim] - 1, pos[dim] + dpos[dim]));`.
boundless, // `pos[dim] += dpos[dim];`.
}; // Notice: to teleport to new positions, `dpos[dim] = newPos[dim] - pos[dim]`. but most sims use relative motions.
protected PosBoundsMode posBounds;
protected Pos bounds = new Pos(0, 0);
protected Pos boundsSlash2 = bounds.slashScalar(2); // Improves execution of inner loops which use this
protected double boundsVolume = bounds.volume();
protected int gridResolution = 100;
protected int[] gridSize = computeGridSize();
public ImmutablePosBounds() {
setPosBoundsMode_(PosBoundsMode.boundless);
}
public ImmutablePosBounds(ImmutablePosBounds o) {
set_(o);
}
public ImmutablePosBounds(PosBoundsMode o, double[] resolution) {
setPosBoundsMode_(o);
setResolution_(resolution);
}
public ImmutablePosBounds(PosBoundsMode o, ImmutablePos resolution) {
setPosBoundsMode_(o);
setResolution_(resolution);
}
@Override
public PosBounds clone() { /* Usage: `ImmutablePosBounds pos = o.clone(); assert o.equals(pos);` */
return new PosBounds(this);
}
protected int[] computeGridSize() {
// int[] gridSize_ = bounds.clone();
int[] gridSize_ = new int[bounds.dims()];
for(int i = 0; gridSize_.length > i; ++i) {
gridSize_[i] = (int)Math.ceil(getBounds(i) / gridResolution);
}
return gridSize_;
}
/* Public setter functions: `class PosBounds extends ImmutablePosBounds`. */
protected void setPosBoundsMode_(PosBoundsMode o) {
posBounds = o;
}
protected void setResolution_(double[] resolution) {
setBounds_(resolution);
boundsSlash2 = bounds.slashScalar(2);
boundsVolume = bounds.volume();
gridSize = computeGridSize();
}
protected void setResolution_(ImmutablePos resolution) {
setResolution_(resolution.pos);
}
protected void set_(ImmutablePosBounds o) {
setPosBoundsMode_(o.posBounds);
setResolution_(o.bounds);
setGridResolution_(o.gridResolution);
}
protected void setBounds_(double[] resolution) {
// assert null != resolution; // Notice: chose to use `java`'s default solution for `clone(null)`
bounds = new Pos(resolution);
}
protected void setBounds_(ImmutablePos resolution) {
bounds = resolution.clone();
}
protected void setBounds_(int index, double resolution) {
bounds.set(index, resolution);
}
protected void setBoundsSlash2_(double[] resolution) {
// assert null != resolution; // Notice: chose to use `java`'s default solution for `clone(null)`
boundsSlash2 = new Pos(resolution);
}
protected void setBoundsSlash2_(ImmutablePos resolution) {
boundsSlash2 = resolution.clone();
}
protected void setBoundsSlash2_(int index, double resolution) {
boundsSlash2.set(index, resolution);
}
protected void setGridResolution_(int resolution) {
gridResolution = resolution;
gridSize = computeGridSize();
}
/* Public getter functions: */
public PosBoundsMode getPosBoundsMode() { return this.posBounds; }
public double[] getBounds() {
assert null != bounds;
assert null != bounds.pos;
return bounds.pos; // Notice: should `return bounds.pos.clone()` to enforce "Immutable", but this is used in inner loops
}
public double[] getBoundsSlash2() { // Caches `getBounds()[dim] / 2` for physics uses (improves inner loops).
assert null != boundsSlash2;
assert null != boundsSlash2.pos;
return boundsSlash2.pos; // Notice: should `return boundsSlash2.pos.clone()` to enforce "Immutable", but this is used in inner loops
}
public ImmutablePos getBoundsPos() {
assert null != bounds; // Notice: redundant (since `java` checks for `null` dereferences), but documents what this does
return bounds;
}
public double getBounds(int index) {
// assert null != bounds; // Notice: chose to use `java`'s default `throw`
// assert null != bounds.pos; // Notice: chose to use `java`'s default `throw`
return bounds.pos[index];
}
public ImmutablePos getBoundsSlash2Pos() { // Caches `getBounds().slash(2)` for physics uses (improves inner loops).
assert null != boundsSlash2; // Notice: redundant (since `java` checks for `null` dereferences), but documents what this does
return boundsSlash2; // Notice: should `return boundsSlash2.clone()` to enforce "Immutable", but this is used in inner loops
}
public double getBoundsSlash2(int index) {
// assert null != bounds; // Notice: chose to use `java`'s default `throw`
// assert null != bounds.pos; // Notice: chose to use `java`'s default `throw`
return boundsSlash2.pos[index];
}
public double getBoundsVolume() {
return boundsVolume;
}
public int getGridSize(int index) {
return gridSize[index];
}
public int[] getGridSize() {
return gridSize.clone();
}
/* Comparison functions: */
@Override
public boolean equals(Object o) { /* Usage: `if(!equals(o)) { System.err.println("non-similar values"); }` */
if(null == o || !(o instanceof ImmutablePosBounds)) {
return false;
}
return equals((ImmutablePosBounds)o);
}
public boolean equals(ImmutablePosBounds o) { /* Usage: is `equals(Object o)` with low CPU use */
return o.posBounds == posBounds &&
o.bounds.equals(bounds) &&
o.gridResolution == gridResolution;
// o.boundsSlash2.equals(boundsSlash2) &&
// o.boundsVolume == boundsVolume &&
}
@Override
public int hashCode() { /* Usage: `if(hashCode() != hashCode(o)) { System.err.println("non-similar values"); }` */
return posBounds.hashCode() ^ bounds.hashCode() ^ gridResolution; // Notice: if `boundsSlash2` is independent of `bounds`, then this should include ` ^ boundsSlash2.hashCode()`
}
/* Public `ImmutablePosBounds` functions, which accept `double[]` (future versions also accept `ImmutablePos`): */
public String posOutOfBoundsStr(double[] pos, String posStr) {
return "`" + posStr + " = " + Arrays.toString(pos) + ";` `getBounds() = " + bounds.toString() + ";` (`grid = new ArrayList[" + gridSize[0] + "][" + gridSize[1] + "];`), so `" + posStr + "` is out of bounds.";
}
public String posOutOfBoundsStr(ImmutablePos pos, String posStr) {
return "`" + posStr + " = " + Arrays.toString(pos.pos) + ";` `getBounds() = " + bounds.toString() + ";` (`grid = new ArrayList[" + gridSize[0] + "][" + gridSize[1] + "];`), so `" + posStr + "` is out of bounds.";
}
public double[] posDiff(double[] pos, double[] o) {
if(PosBoundsMode.wrapAroundResolution == getPosBoundsMode()) {
double[] posDiff = new double[pos.length];
for(int i = 0; pos.length > i; ++i) { /* Notice: ensure that `java` [unrolls this](https://github.com/SwuduSusuwu/SusuPosts/blob/preview/posts/Physics_sims_which_structures_to_use.md#separate-variables-versus-dim-lists) */
posDiff[i] = pos[i] - o[i];
if(boundsSlash2.pos[i] < posDiff[i]) {
posDiff[i] -= bounds.pos[i];
} else if(-boundsSlash2.pos[i] > posDiff[i]) {
posDiff[i] += bounds.pos[i];
}
}
return posDiff;
} else {
return new double[] {pos[0] - o[0], pos[1] - o[1]};
}
}
public Pos posDiff(ImmutablePos pos, ImmutablePos o) {
Pos posDiff = pos.minus(o);
if(PosBoundsMode.wrapAroundResolution == getPosBoundsMode()) {
for(int i = 0; pos.pos.length > i; ++i) { /* Notice: ensure that `java` [unrolls this](https://github.com/SwuduSusuwu/SusuPosts/blob/preview/posts/Physics_sims_which_structures_to_use.md#separate-variables-versus-dim-lists) */
if(boundsSlash2.pos[i] < posDiff.pos[i]) {
posDiff.pos[i] -= bounds.pos[i];
} else if(-boundsSlash2.pos[i] > posDiff.pos[i]) {
posDiff.pos[i] += bounds.pos[i];
}
}
}
return posDiff;
}
public boolean isPosInBounds(double[] pos) throws IllegalArgumentException {
if(bounds.dims() != pos.length) {
throw new IllegalArgumentException("`bounds.dims() != pos.length`");
} // TODO: If this test is used at the start of all `pos*()` functions, replace `[]` with `Pos2`, unless optimizer stores this.
for(int i = 0; i < pos.length; i++) {
if(0 > pos[i] || bounds.at(i) <= pos[i]) {
return false;
}
} // TODO: replace `for(...) {...}` with `switch(pos.length) { case 2: ... }`, unless optimizer does this.
return true;
}
public boolean isPosInBounds(ImmutablePos pos) throws IllegalArgumentException {
if(getBoundsPos().dims() != pos.dims()) {
throw new IllegalArgumentException("`getBoundsPos().dims() != pos.dims()`");
} // TODO: If this test is used at the start of all `pos*()` functions, replace `[]` with `Pos2`, unless optimizer stores this.
for(int i = 0; i < pos.dims(); i++) {
if(0 > pos.pos[i] || bounds.pos[i] <= pos.pos[i]) {
return false;
}
} // TODO: replace `for(...) {...}` with +`ImmutablePos::isInBounds(ImmutablePos bounds)`, which can use `switch(pos.length) { case 2: ... }` or specialized virtual functions
return true;
}
public boolean posBound(double[] pos) throws IllegalArgumentException { // If `PosBoundsMode.boundless != posBounds`, this ensures the invariant `0 <= pos[dim] && FishSim.getBounds()[dim] > pos[dim]` is established.
switch(getPosBoundsMode()) { // `PosBoundsMode.` is omitted from all `case`s, to support old `java --source` versions
case invalidArgumentException:
if(!isPosInBounds(pos)) {
throw new IllegalArgumentException(posOutOfBoundsStr(pos, "double[] pos"));
// return false; // Notice: unsure of codeflow after the exception is handled. This gives an error if uncommented, but without this, if the exception is handled, the function will fall through to `return true`.
}
break;
case wrapAroundResolution:
pos[0] = ((pos[0] % bounds.pos[0]) + bounds.pos[0]) % bounds.pos[0];
pos[1] = ((pos[1] % bounds.pos[1]) + bounds.pos[1]) % bounds.pos[1];
break;
case clampToResolution:
pos[0] = Math.max(0, Math.min(bounds.pos[0] - 1, pos[0])); // TODO: if `java` does not precompute `bounds.at(dim) - 1`, store `boundsMinus1[]`
pos[1] = Math.max(0, Math.min(bounds.pos[1] - 1, pos[1]));
break;
case boundless:
return isPosInBounds(pos);
default:
throw new IllegalArgumentException("Unknown `enum PosBoundsMode`: " + posBounds); // [The compiler does this for you](https://codingtechroom.com/question/what-exception-compiler-unknown-enum-values-switch-expressions), so this just serves to document the lack of `default` codeflow.
}
return true;
}
public boolean posBound(Pos pos) throws IllegalArgumentException { // If `PosBoundsMode.boundless != posBounds`, this ensures the invariant `0 <= pos[dim] && FishSim.getBounds()[dim] > pos[dim]` is established.
assert getBoundsPos().dims() == pos.dims(); // Notice: `bounds` for volumetric physics allows 2-dimensional `pos`, but `posBounds` does not implement such codeflow
switch(getPosBoundsMode()) { // `PosBoundsMode.` is omitted from all `case`s, to support old `java --source` versions
case invalidArgumentException:
if(!isPosInBounds(pos)) {
throw new IllegalArgumentException(posOutOfBoundsStr(pos, "Pos pos"));
// return false; // Notice: unsure of codeflow after the exception is handled. This gives an error if uncommented, but without this, if the exception is handled, the function will fall through to `return true`.
}
break;
case wrapAroundResolution:
pos.plusEquals(getBoundsPos());
pos.moduloEquals(getBoundsPos());
break;
case clampToResolution:
pos.pos[0] = Math.max(0, Math.min(getBounds()[0] - 1, pos.pos[0])); // TODO: +`Pos::{maxEquals(Pos), minEquals(Pos)}` or +`Pos::clamp(pos)`
pos.pos[1] = Math.max(0, Math.min(getBounds()[1] - 1, pos.pos[1]));
break;
case boundless:
return isPosInBounds(pos);
default:
throw new IllegalArgumentException("Unknown `enum PosBoundsMode`: " + posBounds); // [The compiler does this for you](https://codingtechroom.com/question/what-exception-compiler-unknown-enum-values-switch-expressions), so this just serves to document the lack of `default` codeflow.
}
return true;
}
};./susuwu/PosBounds.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> stores the newest version of `./susuwu/PosBounds.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; /* Usage: `import susuwu.PosBounds;` */
/**
* `class PosBounds` says how sims enforce position bounds.
* Usage: `PosBounds posBounds;`
* `class PosBounds` does not use generics since [`java` generics do not allow primitives](https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types), but `:%s/double/float/` in `vim` will produce the `float` version (`:%s/double/long/` produces the `long` version). [Valhalla is a possible solution for this](https://openjdk.org/jeps/218)
* Some `assert`s follow, thus document which arguments to use with this (without `-enableassertions`, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
*/
public class PosBounds extends ImmutablePosBounds { /* Usage: replaces `double fooDistance; double fooFactor;` with `PosBounds fooPosBounds;` */
/* Member variables & constructors: */
public PosBounds() {
setPosBoundsMode_(PosBoundsMode.boundless);
}
public PosBounds(ImmutablePosBounds o) {
set_(o);
}
public PosBounds(PosBoundsMode o, double[] resolution) {
setPosBoundsMode_(o);
setResolution_(resolution);
}
public PosBounds(PosBoundsMode o, ImmutablePos resolution) {
setPosBoundsMode_(o);
setResolution_(resolution);
}
@Override
public PosBounds clone() { /* Usage: `PosBounds pos = o.clone(); assert o.equals(pos);` */
return new PosBounds(this);
}
/* Public setter functions: `class PosBounds extends PosBounds`. */
public void setPosBoundsMode(PosBoundsMode o) {
setPosBoundsMode_(o);
}
public void set(ImmutablePosBounds o) {
set_(o);
}
public void setBounds(double[] resolution) {
setResolution_(resolution);
}
public void setBounds(int index, double resolution) {
bounds.set(index, resolution);
boundsSlash2.set(index, resolution / 2);
boundsVolume = bounds.volume();
gridSize[index] = (int)Math.ceil(bounds.at(index) / gridResolution);
}
public void setGridResolution(int resolution) {
setGridResolution_(resolution);
}
};./susuwu/SimUsages.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> has the newest version of `./susuwu/SimUsages.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; /* Usage: `import susuwu.SimUsages;` */
import javafx.application.Platform; /* `Platform.runLater(...)` */
import javafx.scene.layout.Pane; /* `Pane pane` */
import javafx.scene.paint.Color; /* `Color.WHITE` */
import javafx.scene.text.Text; /* `Text fpsText` */
/**
* {@code class SimUsages} shows {@code FpsTextMode} statistics such as {@code fps} or {@code ms}.
* Requirements: some render loop (for measurements). Is not specific to the renderer used.
* Was produced for {@code susuwu.FishSim}, so the text (plus comments) assume the organisms are {@code class Fish}, but {@code SimUsages} is not specific to {@code class Fish}
* Some {@code assert}s follow, thus document which arguments to use with this (without {@code -enableassertions}, thus are not enforced).
* Some "Usage:" comments follow, which document how to use this.
* Usage: {@code SimUsages usages = SimUsages(Pane); usages.show(); usages.fpsTextMode = FpsTextMode.fps.value | FpsTextMode.ms.value;}
*/
public class SimUsages {
/* `public` members */
public long fpsTextMode = FpsTextMode.allUsages.value; // Usage `usages.fpsTextModeFps = FpsTextMode.fps.value;` to just show `fps`
public double secondsPerFpsTextRefresh = 1.0; // Usage: `usages.secondsPerFpsTextRefresh = 0.2;` to give more current values, or `= 2.0;` to give more smooth values. In `postRefresh()`: if `secondsPerFpsTextRefresh` elapses, `lastTime = System.nanoTime(); fpsTextRefresh();``
public double renderMs = Double.NaN; // Usage: `functionWhichUsesRenderMs(usages.renderMs);`. Stores average **ms** from `startRender()` to `postRender()` (`renderNs / renderCounter / 1_000_000.0`)
public double physicsMs = Double.NaN; // Usage: `functionWhichUsesPhysicsMs(usages.physicsMs);`. Stores average **ms** from `startPhysics()` to `postPhysics()` (`physicsNs / physicsCounter / 1_000_000.0`)
public double fps = Double.NaN; // Usage: `functionWhichUsesFps(usages.fps);`. Stores `renderCounter / (System.nanoTime() - lastTime) / 1_000_000_000.0`
/* `private` or almost-`private` members */
public long lastTime = System.nanoTime(); // Stores `System.nanoTime()` when `renderCounter = 0`.
public int refreshCounter = 0; // Sum of `postRefresh()` uses since `SimUsages(Pane)`.
public int renderCounter = 0; // Sum of `postRender()` uses since `lastTime = System.nanoTime()`.
public int physicsCounter = 0; // Sum of `postPhysics()` uses since `lastTime = System.nanoTime()`.
public long physicsNs = -1; // Sum of `nanoTime()` at `postRender()` minus `nanoTime()` at `startRender()` since `lastTime = System.nanoTime()`.
public long renderNs = -1; // Sum of `nanoTime()` at `postRender()` minus `nanoTime()` at `startRender()` since `lastTime = System.nanoTime()`.
private Pane root;
public SimUsages(Pane pane) {
this.root = pane;
this.root.getChildren().add(fpsText);
}
static public enum FpsTextMode { // `FpsTextMode` says which resources `fpsText` will show.
none (0 ), // `fpsText = "";`
fps (1 << 0), // `fpsText` += `fps` "FPS";
ms (2 << 1), // `fpsText` += `ms` "ms"; /* Notice: `ms = 1000 / fps;`, so includes idle CPU */
msSpec (1 << 2), // `fpsText` += `renderMs` "renderMs," `physicsMs` "physicsMs"; /* Notice: uses `System.nanoTime()`, does not include idle CPU */
msFish (1 << 3), // `fpsText` += `renderMs / fishShown` "renderMs / Fish shown," `physicsMs / fishListSize` "physicsMs / Fish";
fish (1 << 4), // `fpsText` += `fishListSize` "Fish";
fishShown (1 << 5), // `fpsText` += `fishShown` "Fish shown";
allUsages (FpsTextMode.fps.value | FpsTextMode.ms.value | FpsTextMode.msSpec.value | FpsTextMode.msFish.value | FpsTextMode.fish.value | FpsTextMode.fishShown.value); // shows as all supported usages
long value; // Stores bitwise-or of those.
FpsTextMode(long value) { this.value = value; }
}; // TODO: replace manual bitshifts with `java.util.EnumSet<E>`?
private Text fpsText = new Text("0 FPS");
public void show() {
fpsText.setX(10);
fpsText.setY(30);
fpsText.setFill(Color.WHITE);
}
/* Measurement funtions
* Usage: ```
* refreshLoop() {
* usages.startRefresh();
* usages.startPhysics(); physics(fishList); usages.postPhysics();
* usages.startRender(); render(visibleFish); usages.postRender();
* usages.postRefresh(System.nanoTime(), visibleFish.size(), fishList.size());
* }```
* "Refresh loop" is the loop which invokes the render loop plus the physics loop. `class SimUsages` does not assume that the physics loop is invoked as often as the render loop is. If the renderer has its own separate loop, the render loop is the refresh loop.
*/
public void startRefresh() { // Usage: `startRender();` at start of refresh loop.
}
public void postRefresh(long now, long fishShown, long fishListSize) { // Usage: `postRefresh(System.nanoTime(), visibleObjects.size(), physisObjects.size());`
double elapsed = (now - lastTime) / 1_000_000_000.0;
if(elapsed >= secondsPerFpsTextRefresh) {
lastTime = now;
fps = renderCounter / elapsed;
renderMs = renderNs / renderCounter / 1_000_000.0;
physicsMs = physicsNs / physicsCounter / 1_000_000.0;
Platform.runLater(() -> fpsTextRefresh(fishShown, fishListSize));
renderCounter = 1;
renderNs = -1;
physicsCounter = 1;
physicsNs = -1;
}
refreshCounter++;
}
private long renderNsStart, physicsNsStart;
public void startRender() { // Usage: `startRender();` at start of render loop
renderNsStart = System.nanoTime();
}
public void preSynchro() { // Usage: `startRender(); ... startSynchro(); SdlGles2.glClear(...); postSynchro();`
renderNsStart += System.nanoTime();
} // TODO: reduce `preSynchro()` to no-op, improve `postSynchro()` to subtract actual Virtual Synchronization time from `renderNsStat` (so the time to clear buffers is included).
public void postSynchro() { // Usage: `startRender(); ... startSynchro(); SdlGles2.glClear(...); postSynchro();`
renderNsStart -= System.nanoTime(); // Purpose: subtracts vertical synchronization time from "drawMS` (which is computed from `renderNs`, which is computed from `renderNsStart`). Problem: `glClear()` does not just wait for Virtical Synchronization, but also clears the buffer, which this will also subtract from `renderNs`.
}
public void postRender() { // Usage: `startRender();` at closure of render loop
renderNs += System.nanoTime() - renderNsStart;
renderCounter++;
}
public void startPhysics() { // Usage: `startPhysics();` at start of physics loop
physicsNsStart = System.nanoTime();
}
public void postPhysics() { // Usage: `startPhysics();` at closure of physics loop
physicsNs += System.nanoTime() - physicsNsStart;
physicsCounter++;
}
private void fpsTextRefresh(long fishShown, long fishListSize) { /* Usage: `usages.fpsTextModeFps(visibleFish.size(), fishList.size())` */
boolean fpsTextModeFps = (0 != (FpsTextMode.fps.value & fpsTextMode));
boolean fpsTextModeMs = (0 != (FpsTextMode.ms.value & fpsTextMode));
boolean fpsTextModeMsSpec = (0 != (FpsTextMode.msSpec.value & fpsTextMode));
boolean fpsTextMsSpecFish = (0 != ((FpsTextMode.msSpec.value | FpsTextMode.msFish.value) & fpsTextMode)); // TODO: replace manual bitshifts with `java.util.EnumSet<E>`?
boolean fpsTextModeMsFish = (0 != (FpsTextMode.msFish.value & fpsTextMode));
boolean fpsTextModeFish = (0 != (FpsTextMode.fish.value & fpsTextMode));
boolean fpsTextModeFishShown = (0 != (FpsTextMode.fishShown.value & fpsTextMode));
double totalMs = 1 / fps * 1000;
String fpsTextStr = "";
String strSep = ", ", strJoin = " (";
if(FpsTextMode.none.value == fpsTextMode) { return; }
if(fpsTextModeFps) {
fpsTextStr += String.format("%4.2f FPS" + strSep, fps);
}
if(fpsTextModeMs) {
fpsTextStr += String.format("%4.2f MS" + (fpsTextMsSpecFish ? strJoin : strSep), totalMs);
}
if(fpsTextModeMsSpec) {
fpsTextStr += String.format("%4.2f drawMS, %4.2f physicsMS", renderMs, physicsMs);
fpsTextStr += (fpsTextModeMsFish ? strSep : "");
}
if(fpsTextModeMsFish) {
fpsTextStr += String.format("%2.4f drawMS / Fish shown, %2.4f physicsMS / Fish", renderMs / fishShown, physicsMs / fishListSize);
}
if(fpsTextMsSpecFish) {
if(0 != ((FpsTextMode.ms.value & fpsTextMode))) {
fpsTextStr += ")";
}
fpsTextStr += strSep;
}
if(fpsTextModeFish) {
fpsTextStr += String.format("%4d Fish", fishListSize);
fpsTextStr += (fpsTextModeFishShown ? strJoin : strSep);
}
if(fpsTextModeFishShown) {
fpsTextStr += String.format(fpsTextModeFish ? "%4d shown)" : "%4d Fish shown", fishShown);
fpsTextStr += strSep;
}
fpsText.setText(fpsTextStr.substring(0, fpsTextStr.length() - strSep.length()));
}
};./susuwu/FishSim.java
/* Attribution (henceforth "*this attribution*", whose syntax is *Markdown*): 2024 [Swudu Susuwu](https://swudususuwu.substack.com)
* <https://github.com/SwuduSusuwu/SusuJava/> has the newest version of `./susuwu/FishSim.java` (henceforth "*this source code*").
* If *this attribution* is shown, *this source code* allows all uses. *This attribution* constitutes the most permissive which is compatible with [*GPLv2*](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) + [*Apache 2*](https://www.apache.org/licenses/LICENSE-2.0.html), which is suitable for personal use (also suitable for school use).
* If *this attribution* is not professional enough for business use: businesses can use *this source code* through included versions of [*GPLv2*](./LICENSE_GPLv2), [*Apache 2*](./LICENSE), or through both of those.
*/
package susuwu; /* Usage: `import susuwu.FishSim;` */
import javafx.animation.Animation;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import susuwu.SimUsages; /* `class SimUsages`, `enum FpsTextMode` */
import susuwu.Calculus; /* `Calculus.pow2()` */
import susuwu.Forces; /* `class Forces implements java.lang.Cloneable` */
import susuwu.ImmutablePosBounds; /* `enum PosBoundsMode`: which stores how sims enforce bounds. */
import susuwu.PosBounds; /* `class PosBounds : extends ImmutablePosBounds`, `PosBounds.set*(PosBounds*)` */
import susuwu.ImmutablePos; /* `class ImmutablePos implements java.util.RandomAccess` */
import susuwu.Pos; /* `class Pos extends ImmutablePos` */
import susuwu.Pos2; /* `class Pos extends Pos` */
// public static class Pos2 extends double[2] {} // `{Pos2[0], Pos2[1]}` is `{x, y}` position (or resolution), or is `{pos[0], pos[0]}` motion (derivative of position), or is is `{d2x, d2y}` acceleration (derivative number 2). This was supposed to do what `typedef` does (wish for future-proof (limitless dimensions) virtual `class` with functions for numerous transforms).
// TODO: test how much of `java`'s [static `Array` overhead](https://github.com/SwuduSusuwu/SusuPosts/blob/preview/posts/Physics_sims_which_structures_to_use.md#separate-variables-versus-dim-lists) `java`'s toolkit optimizes for you. If performance is a problem, choose a new approach to use.
/**
* Simple [*JavaFX*](https://github.com/openjdk/jfx) fish sim, which includes reusable {@code public class}s (for new sims to use). Most of the reusable {@code public class}s are in other {@code .java} sources for {@code package susuwu}
* This ([`./susuwu/FishSim.java`](./FishSim.java)) uses pseudo-*Markdown* for comments, but [`./posts/FishSim.md`](../posts/FishSim.md) is the actual [*Markdown*](https://github.github.com/gfm/) document for this.
* Notice: replaced most of [*Solar-Pro-2*'s original `FishSim.java`](https://github.com/SwuduSusuwu/SusuJava/blob/solarPro2FishSim/susuwu/FishSim.java), as [`./posts/FishSim.md#intro`](../posts/FishSim.md#intro) documents (plus [*GitHub*'s `/compare/` tool shows](https://github.com/SwuduSusuwu/SusuJava/compare/solarPro2FishSim..susuFishSim#diff-8c440bb92bc6939e1450542897e0bbb1a8737b93808ea63ed32784edfacef4b4).
*/
public class FishSim extends Application {
public enum PhysicsMode { // `PhysicsMode` says how to execute `updateFish()`
synchronousHomo, // `updateFish()` once per `refreshLoop()`.
synchronousInterval, // `updateFish()` per `positionInterval` `refreshLoop()`s.
asynchronousHomo, // `executor.submit(() -> updateFish());` once per `refreshLoop()`.
asynchronousInterval, // `executor.submit(() -> updateFish());` per `positionInterval` `refreshLoop()`s.
separateUnbound, // `new AnimationTimer() { public void handle(long now) { updateFish(); }` // Notice: with `GLES2` this has an implicit bound to the monitor refresh (Virtual Synchronization, which `SimUsages` does not count towards "drawMS").
separateFps, // `Timeline timeline = new Timeline( new KeyFrame(Duration.millis(1000.0 / physicsRefreshHertz), event -> { updateFish(); })`
}
private static PhysicsMode monitorRefreshMode = PhysicsMode.separateFps; // `monitorRefreshMode` must use `.separateUnbound` or `.separateFps`.
private static PhysicsMode physicsMode = PhysicsMode.separateFps; // Notice: if `PhysicsMode.*Interval`, must set `positionInterval`. if `PhysicsMode.separateFps`, must set `physicsRefreshHertz`.
static ReentrantLock renderFishLock = new ReentrantLock();
static ReentrantLock updateFishLock = new ReentrantLock();
// TODO: remove `static` from {`resolution`, `bounds`}, to allow to remove `static` from {`setResolution()`, `renderFishLock, `updateFishLock`}, so `FishSim` allows numerous windows
public static boolean setResolution(int[] newResolution) { /* Notice: invalidates references to old `bounds`, `*Slash2` addresses. */ // TODO: remove `static` from {`resolution`, `bounds`}, to allow to remove `static` from {`setResolution()`, `renderFishLock, `updateFishLock`}, so `FishSim` allows numerous windows
assert 2 == newResolution.length;
assert 0 < newResolution[0]; //TODO: allow "headless" instances with `resolution = {0, 0}`?
assert 0 < newResolution[1];
updateFishLock.lock();
renderFishLock.lock();
resolution = newResolution;
resolutionf.pos[0] = resolution[0]; resolutionf.pos[1] = resolution[1];
resolutionfSlash2 = resolutionf.slashScalar(2); /* Notice: invalidates references which store the old address to `resolutionfSlash2`. */
resVolume = (int)Math.round(resolutionf.volume()); /* Notice: uses `Pos::volume()` since simple source code is less bug prone. `Math.round` ensures 24-bit mantissas give accurate values */
posBounds.setBounds(resolutionf.starScalar(boundsResolutionFactor).pos); // TODO: if sure that no functions store references to the original instance, replace the above row with this (since simple source code is less bug prone)
// canvas = new Canvas(resolution[0], resolution[1]); // TODO: replace with `canvas.setWidth(resolution[0]); canvas.setHeight(resolution[1]);`?
// gc = canvas.getGraphicsContext2D();
// scene = new Scene(root, resolution[0], resolution[1], Color.LIGHTBLUE); // replace with `scene.widthProperty().bind(primaryStage.widthProperty());`?
// stage.setScene(scene);
renderFishLock.unlock();
updateFishLock.unlock();
return true;
}
private static int[] resolution = {1280, 720};
private static Pos2 resolutionf = new Pos2(resolution[0], resolution[1]);
private static Pos resolutionfSlash2 = resolutionf.slashScalar(2); /* Improves execution of inner loops which use this. Notice: `setResolution(newResolution)` invalidates stored references to `resolutionfSlash2` */
private static int resVolume = (int)Math.round(resolutionf.volume()); /* Notice: uses `Pos::volume()` since simple source code is less bug prone. `Math.round` ensures 24-bit mantissas give accurate values */
private static double boundsResolutionFactor = (1_000_000 > resVolume ? 2.0 : 1.2); // Ocean is `resolution[dim] * boundsResolutionFactor`. `2.0` gives more room for natural oceans, but old laptops with huge resolutions (such as `{2200, 1200}`) must use `1.2` so the load is low enough for old CPUs to process). TODO: include short benchmark (on startup) to set `boundsResolutionFactor` to optimal value, or reduce CPU use for unshown `Fish` (`if(!fish.isVisible)`, then execute `updateFish` just once per second (1 hertz), with larger steps).
private static PosBounds posBounds = new PosBounds(PosBounds.PosBoundsMode.wrapAroundResolution, resolutionf.starScalar(boundsResolutionFactor)); // for simple sims, use `resolutionf.clone()`
private static double fishVolume = 200; // Uses resolution of `Fish::render()`.
private static double fishLengthsSep = 62; // Average `Fish`-lengths distance from `Fish` to `Fish`.
private static double fishPerVolume = 1 / fishVolume / fishLengthsSep; // `Fish` per volume (for 2D, volume is resolution).
private static int fishCount = (int)(posBounds.getBoundsVolume() * fishPerVolume);
private static int gridResolution = 100; // Notice: set this to `Colllections.max({forces*.distance})` (which should equal what most sims call "view distance"), so that all relevent `Fish` are processed.
private static int positionInterval = 2; // The `refreshCounter` per `Fish::applyFlockingRulesUpdate()`
public static double monitorRefreshHertz = 60.0; // The `SimUsages.fps` to wish for // Notice: since this limits `SimUsages.fps` to `monitorRefreshHertz`, this prevents benchmarks which use `FpsTextMode.fps` (or `FpsTextMode.ms`). Benchmarks can still use `FpsTextMode.msSpec` (or `FpsTextMode.msFish`).
public static double physicsRefreshHertz = monitorRefreshHertz / positionInterval; // The `1 / SimUsages.physicsMs` to wish for // Notice: unknown what `javafx.animation.Timeline` does if `physicsRefreshHertz > (1 / SimUsages.physicsMs)`, but guess thus stalls or consumes multiple executors
private List<Fish> fishList = new ArrayList<>();
private int fishShown = 0;
private List<Fish>[][] grid; /* `listToPartitions(List<>[][] grid, List<> list)` uses this */
private Random random = new Random();
private Pane root = new Pane();
private Canvas canvas = new Canvas(resolution[0], resolution[1]);
private GraphicsContext gc = canvas.getGraphicsContext2D();
private Stage stage;
SimUsages simUsages = new SimUsages(root);
// simUsages.fpsTextMode = FpsTextMode.allUsages.value; // TODO: "error: <identifier> expected" solution
private ExecutorService executor = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Initialize fish
for(int i = 0; i < fishCount; i++) {
Pos2 pos = new Pos2(random.nextDouble() * posBounds.getBounds(0), random.nextDouble() * posBounds.getBounds(1));
Pos2 dpos = new Pos2((random.nextDouble() * 2 - 1) * Fish.dposMax, (random.nextDouble() * 2 - 1) * Fish.dposMax);
fishList.add(new Fish(pos, dpos, Color.color(random.nextDouble(), random.nextDouble(), random.nextDouble())));
}
root.getChildren().add(canvas);
Scene scene = new Scene(root, resolution[0], resolution[1], Color.LIGHTBLUE);
stage = primaryStage;
primaryStage.setScene(scene);
primaryStage.setTitle("Fish Simulation (Boids)");
primaryStage.setResizable(false);
primaryStage.show();
simUsages.show();
posBounds.setGridResolution(gridResolution);
grid = new ArrayList[posBounds.getGridSize(0)][posBounds.getGridSize(1)]; /* `listToPartitions(List<>[][] grid, List<> list)` uses this */
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[i].length; j++) {
grid[i][j] = new ArrayList<>();
}
}
// Start animation loop
switch(monitorRefreshMode) { // `PhysicsMode.` is omitted from all `case`s, to support old `java --source` versions
case separateUnbound:
new AnimationTimer() {
@Override
public void handle(long now) { refreshLoop(now); }
}.start(); // Notice: replace `Timeline` with this for benchmarks which use `FpsTextMode.fps` (or `FpsTextMode.ms`).
break;
case separateFps:
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(1000.0 / monitorRefreshHertz), event -> { refreshLoop(System.nanoTime()); })
); // Notice: since this limits `SimUsages.fps` to `monitorRefreshHertz`, this prevents benchmarks which use `FpsTextMode.fps` (or `FpsTextMode.ms`). Benchmarks can still use `FpsTextMode.msSpec` (or `FpsTextMode.msFish`).
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
break;
default:
throw new IllegalArgumentException("Unsupported `PhysicsMode monitorRefreshMode`: " + monitorRefreshMode);
}
switch(physicsMode) { // `PhysicsMode.` is omitted from all `case`s, to support old `java --source` versions
case separateUnbound: // Notice: `GLES2` version uses Vertical Synchronization, which `SimUsages` subtracts from `renderNs` (does not count towards resource usage).
new AnimationTimer() {
@Override
public void handle(long now) { updateFish(); }
}.start();
break;
case separateFps:
Timeline loopPerSecond = new Timeline(
new KeyFrame(Duration.millis(1000.0 / physicsRefreshHertz), event -> { updateFish(); })
);
loopPerSecond.setCycleCount(Animation.INDEFINITE);
loopPerSecond.play();
break;
}
}
private void refreshLoop(long now) {
simUsages.startRefresh();
switch(physicsMode) { // `PhysicsMode.` is omitted from all `case`s, to support old `java --source` versions
case synchronousHomo:
updateFish();
break;
case synchronousInterval:
if(simUsages.refreshCounter % positionInterval == 0) {
updateFish();
}
break;
case asynchronousHomo:
executor.submit(() -> updateFish());
break;
case asynchronousInterval:
if(simUsages.refreshCounter % positionInterval == 0) {
executor.submit(() -> updateFish());
}
break;
case separateUnbound:
case separateFps:
break; // no-op for both, since `start(Stage primaryStage)` processes thus
default:
throw new IllegalArgumentException("Unsupported `PhysicsMode physicsMode`: " + physicsMode);
}
renderFish();
simUsages.postRefresh(now, fishShown, fishList.size());
}
private void outOfBounds(String function, Fish fish) {
/* Notice: `outOfBounds()` has numerous sensible actions other than to print to `stderr`: `fish.die()`, `fish.stop()`, `fish.reverse()`, `fish.wrapAround()` */
System.err.println(function + ": " + posBounds.posOutOfBoundsStr(fish.pos, "Fish.pos"));
}
/* Spatial partitioning (simple grid system). TODO: generic version of this (accept all `class`s with `#isInBounds` plus `#pos`). */
private void listToPartitions(List<Fish>[][] grid, List<Fish> list) {
assert grid.length == (int)Math.ceil(posBounds.getBounds(0) / gridResolution);
assert grid[0].length == (int)Math.ceil(posBounds.getBounds(1) / gridResolution);
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[i].length; j++) {
grid[i][j].clear();
}
}
for(Fish fish : list) { /* Assign list members to grid sections */
if(fish.isInBounds) {
int[] gridPos = {(int) (fish.pos.pos[0] / gridResolution), (int) (fish.pos.pos[1] / gridResolution)};
grid[gridPos[0]][gridPos[1]].add(fish); // if `gridPos` is not in bounds, this will `throw new IndexOutOfBoundsException()`. But `Fish.setPos()` uses `PosBounds::posBounds.posBound()` which, which ensures `Fish.pos` bounds to `FishSim.resolution`, so this will not `throw`.
}
}
}
private void updateFish() {
updateFishLock.lock();
simUsages.startPhysics();
listToPartitions(grid, fishList);
// Update each fish
for(Fish fish : fishList) {
fish.applyFlockingRules(fishList, grid);
fish.update();
}
simUsages.postPhysics();
updateFishLock.unlock();
}
private void renderFish() {
renderFishLock.lock();
simUsages.startRender();
fishShown = 0;
// simUsages.preSynchro(); // Notice: alternatives: use `SDL_GL_SetSwapInterval(0);`, or move `simUsages.startRender()` below the first use of `SdlGles2`
gc.clearRect(0, 0, resolution[0], resolution[1]);
// simUsages.postSynchro();
for(Fish fish : fishList) {
if(fish.isVisible) { // For `Fish` not shown, this condition improves `SimUsages.fps` (lowers `SimUsages.renderNs`).
fishShown++;
fish.render(gc);
}
}
simUsages.postRender();
renderFishLock.unlock();
}
@Override
public void stop() {
executor.shutdown();
}
public class Fish { /* `static Fish` causes "{posBounds,posBounds.posBound()} cannot be referenced from a static context" (unless those are set to `static`, which prevents `FishSim` from use of separate values with multiple windows) */
public static Forces forcesSeparation = new Forces(22.0, 2.0);
public static Forces forcesSeparationNonsimilar = new Forces(100.0, 2.2);
public static Forces forcesAlignment = new Forces(100.0, 1.0);
public static Forces forcesCohesion = new Forces(100.0, 1.0);
public static Forces forcesBounds = new Forces(100.0, 2.0);
private static double dposMax = 3.0; // Motion lim (limit of derivative of position)
private static double d2Pos = 0.1; // Motion<sup>2</sup> (derivative #2 of position)
private static double isSimilarTolerance = 0.2;
public static boolean applyWallAvoidanceTru = (PosBounds.PosBoundsMode.wrapAroundResolution != posBounds.getPosBoundsMode());
public static boolean redFishAreAggressiveOrPoisonous = true; // changes how `isSimilarTo(Fish other)` uses `color.getRed()`
private Pos pos; // Position
private Pos dpos; // Motion (derivative of position)
private Color color;
public boolean isInBounds;
public boolean isVisible = false; // Just stores `0 <= pos[0] && resolution[0] > pos[0] && 0 <= pos[1] && resolution[1] > pos[1]` for now.
public Fish(ImmutablePos pos, ImmutablePos dpos, Color color) {
this.pos = pos.clone(); // TODO: ensure this clones the actual (specialized) virtual function addresses, which improve CPU use
this.dpos = dpos.clone();
this.color = color;
}
public Fish(Pos pos, Pos dpos, Color color) { /* Notice: uses "placement moves" for {`pos`, `dpos`}. Gives `Fish` ownership of `pos`, ownership of `dpos`. */
this.pos = pos;
this.dpos = dpos;
this.color = color;
}
public boolean isSimilarTo(Fish o) {
// return color.equals(o.color); // Less CPU use, but requires that `fishList` has just a few colors.
// Color colorDis = Color.color(color.getRed() - o.color.getRed(), color.getGreen() - o.color.getGreen(), color.getBlue() - o.color.getBlue()); // Notice: `Color` is more intuitive to use, but was concerned that `java` will not fold this
double[] colorDis = {color.getRed() - o.color.getRed(), color.getGreen() - o.color.getGreen(), color.getBlue() - o.color.getBlue()};
if(redFishAreAggressiveOrPoisonous) {
colorDis[0] = Calculus.pow2(colorDis[0]);
}
// return isSimilarTolerance > (Math.hypot(Math.abs(colorDis[0]), Math.abs(colorDis[1]), Math.abs(colorDis[2]))); // [`Math.hypot()` still does not support > 2 dimensions?](https://esdiscuss.org/topic/how-about-more-args-for-math-hypot). Notice: if you use Euclidean distance, lower `isSimilarTolerance`.
return isSimilarTolerance > (Calculus.pow2(colorDis[0]) + Calculus.pow2(colorDis[1]) + Calculus.pow2(colorDis[2]));
} // TODO: Use a function (such as `javafx.scene.shape.Polygon.getPoints()`) for comparison of vertices. */
public Pos getPosDiff(Fish o) {
return posBounds.posDiff(pos, o.pos);
}
public synchronized void setPos(Pos newPos /* Notice: semantics of "placement move" */) {
isInBounds = posBounds.posBound(newPos); // If `PosBoundsMode.boundless != posBounds.getPosBoundsMode()`, this ensures the invariant `0 <= pos[dim] && PosBounds.getBounds()[dim] > pos[dim]` is established.
isVisible = (0 <= newPos.pos[0] && resolution[0] > newPos.pos[0] && 0 <= newPos.pos[1] && resolution[1] > newPos.pos[1]); // TODO: +`Pos::isLessOrEquals(Pos)`, +`Pos::isMore(pos)`
if((!isInBounds) && PosBounds.PosBoundsMode.boundless != posBounds.getPosBoundsMode()) {
outOfBounds("Fish::setPos", this);
}
pos = newPos; /* Notice: does not use `newPos.clone()` since this function is used in inner loops. After this function returns, `this` has ownership of `newPos`. */
}
public synchronized void setPos(ImmutablePos newPos) {
setPos(newPos.clone()); /* Notice: since this function is used in inner loops, `setPos(Pos newPos)` uses the semantics of "placement move", so if `newPos` is immutable, must clone. */
}
public synchronized void applyFlockingRules(List<Fish> allFish, List<Fish>[][] grid) {
int[] gridPos = {(int) (pos.pos[0] / gridResolution), (int) (pos.pos[1] / gridResolution)};
List<Fish> nearbyFish = new ArrayList<>();
// Check neighboring grid cells
if(PosBounds.PosBoundsMode.wrapAroundResolution == posBounds.getPosBoundsMode()) {
for(int i = gridPos[0] - 1; i <= gridPos[0] + 1; i++) {
for(int j = gridPos[1] - 1; j <= gridPos[1] + 1; j++) {
nearbyFish.addAll(grid[(i + grid.length) % grid.length][(j + grid[0].length) % grid[0].length]);
} /* TODO: move expensive `%`s into outer loop somehow. With `1000 == fishList.size() && 100 == monitorRefreshHertz`, 2 `%`s in inner loop executes 200,000 `%`/s. The most simple solution is to use outer branches to choose from loops which hardcode this, but thus duplicates codeflow. Does `java` do this for you if the loop uses most of the CPU? */
}
} else {
for(int i = Math.max(0, gridPos[0] - 1); i <= Math.min(grid.length - 1, gridPos[0] + 1); i++) {
for(int j = Math.max(0, gridPos[1] - 1); j <= Math.min(grid[0].length - 1, gridPos[1] + 1); j++) {
nearbyFish.addAll(grid[i][j]);
} /* TODO: move expensive `Math.{min,max}`s into outer loop somehow. With `1000 == fishList.size() && 100 == monitorRefreshHertz`, inner loop executes 200,000 `Math.{min,max}`/s */
}
}
applySeparation(nearbyFish);
applyAlignment(nearbyFish);
applyCohesion(nearbyFish);
if(applyWallAvoidanceTru) {
applyWallAvoidance(posBounds.getBounds());
}
}
private synchronized void applySeparation(List<Fish> nearbyFish) {
Pos sepDpos = dpos.zeros();
Pos sepNonsimilarDpos = dpos.zeros();
int count = 0, countNonsimilar = 0;
for(Fish o : nearbyFish) {
if(o != this) {
Pos posDiff = getPosDiff(o);
double dist = posDiff.magnitude(); // Notice: in future, +`Pos::boundHypotenus(o)`
if(isSimilarTo(o)) {
if(forcesSeparation.posIfDistScaleSum(sepDpos, posDiff, dist)) {
count++;
}
} else {
if(forcesSeparationNonsimilar.posIfDistScaleSum(sepNonsimilarDpos, posDiff, dist)) {
countNonsimilar++;
}
}
}
}
if(count > 0) {
sepDpos.slashEqualsScalar(count);
forcesSeparation.dposScaleSum(dpos, d2Pos, sepDpos);
}
if(countNonsimilar > 0) {
sepNonsimilarDpos.slashEqualsScalar(countNonsimilar);
forcesSeparationNonsimilar.dposScaleSum(dpos, d2Pos, sepNonsimilarDpos);
}
}
private synchronized void applyAlignment(List<Fish> nearbyFish) {
Pos avgDpos = dpos.zeros();
int count = 0;
for(Fish o : nearbyFish) {
if(o != this && isSimilarTo(o)) {
Pos posDiff = getPosDiff(o);
double distPow2 = posDiff.magnitudePow2();
if(forcesAlignment.posIfDistPow2Sum(avgDpos, o.dpos, distPow2)) {
count++;
}
}
}
if(count > 0) {
avgDpos.slashEqualsScalar(count);
forcesAlignment.dposScaleSum(dpos, d2Pos, avgDpos);
}
}
private synchronized void applyCohesion(List<Fish> nearbyFish) {
Pos avgPos = pos.zeros();
int count = 0;
for(Fish o : nearbyFish) {
if(o != this && isSimilarTo(o)) {
Pos posDiff = getPosDiff(o);
double distPow2 = posDiff.magnitudePow2();
if(forcesCohesion.posIfDistPow2Sum(avgPos, o.pos, distPow2)) {
count++;
}
}
}
if(count > 0) {
avgPos.slashEqualsScalar(count);
forcesCohesion.dposScaleSum(dpos, d2Pos, posBounds.posDiff(avgPos, pos));
}
}
private synchronized void applyWallAvoidance(double[] res) {
Pos avoidancePos = dpos.zeros();
if(pos.pos[0] < forcesBounds.distance) {
avoidancePos.pos[0] += (forcesBounds.distance - pos.pos[0]);
} else if(pos.pos[0] > res[0] - forcesBounds.distance) {
avoidancePos.pos[0] -= (pos.pos[0] - (res[0] - forcesBounds.distance));
}
if(pos.pos[1] < forcesBounds.distance) {
avoidancePos.pos[1] += (forcesBounds.distance - pos.pos[1]);
} else if(pos.pos[1] > res[1] - forcesBounds.distance) {
avoidancePos.pos[1] -= (pos.pos[1] - (res[1] - forcesBounds.distance));
}
avoidancePos.starEqualsScalar(d2Pos * forcesBounds.factor / forcesBounds.distance);
dpos.plusEquals(avoidancePos);
}
public synchronized void update() {
// Limit speed
double speed = dpos.magnitude();
if(speed > dposMax) {
dpos.slashEqualsScalar(speed);
dpos.starEqualsScalar(dposMax);
}
// Update position
setPos(pos.plus(dpos));
}
public synchronized void render(GraphicsContext gc) {
gc.save();
gc.translate(pos.pos[0], pos.pos[1]);
gc.rotate(Math.toDegrees(Math.atan2(dpos.pos[1], dpos.pos[0])) + 90);
gc.setFill(color);
gc.beginPath();
gc.moveTo(0, -10);
gc.lineTo(-5, 10);
gc.lineTo(-2, 0);
gc.lineTo(2, 0);
gc.lineTo(5, 10);
gc.closePath();
gc.fill();
gc.restore();
}
};
};

