diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java new file mode 100644 index 00000000..1adbd44a --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java @@ -0,0 +1,25 @@ +package at.petrak.hexcasting.api.casting.arithmetic; + +import at.petrak.hexcasting.api.casting.arithmetic.operator.Operator; +import at.petrak.hexcasting.api.casting.math.HexDir; +import at.petrak.hexcasting.api.casting.math.HexPattern; + +public interface Arithmetic { + public String arithName(); + + public abstract Iterable opTypes(); + + public abstract Operator getOperator(HexPattern pattern); + + public static HexPattern ADD = HexPattern.fromAngles("waaw", HexDir.NORTH_EAST); + public static HexPattern SUB = HexPattern.fromAngles("wddw", HexDir.NORTH_WEST); + public static HexPattern MUL = HexPattern.fromAngles("waqaw", HexDir.SOUTH_EAST); + public static HexPattern DIV = HexPattern.fromAngles("wdedw", HexDir.NORTH_EAST); + public static HexPattern ABS = HexPattern.fromAngles("wqaqw", HexDir.NORTH_EAST); + public static HexPattern POW = HexPattern.fromAngles("wedew", HexDir.NORTH_WEST); + public static HexPattern FLOOR = HexPattern.fromAngles("ewq", HexDir.EAST); + public static HexPattern CEIL = HexPattern.fromAngles("qwe", HexDir.EAST); + public static HexPattern MOD = HexPattern.fromAngles("addwaad", HexDir.NORTH_EAST); + public static HexPattern PACK = HexPattern.fromAngles("eqqqqq", HexDir.EAST); + public static HexPattern UNPACK = HexPattern.fromAngles("qeeeee", HexDir.EAST); +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IotaMultiPredicate.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IotaMultiPredicate.java new file mode 100644 index 00000000..8f9ea2dc --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IotaMultiPredicate.java @@ -0,0 +1,50 @@ +package at.petrak.hexcasting.api.casting.arithmetic; + +import at.petrak.hexcasting.api.casting.iota.Iota; + +@FunctionalInterface +public interface IotaMultiPredicate { + boolean test(Iterable iotas); + + static IotaMultiPredicate all(IotaPredicate child) { + return new All(child); + } + static IotaMultiPredicate bofa(IotaPredicate first, IotaPredicate second) { + return new Any(first, second); + } + static IotaMultiPredicate any(IotaPredicate needs, IotaPredicate fallback) { + return new Any(needs, fallback); + } + record Bofa(IotaPredicate first, IotaPredicate second) implements IotaMultiPredicate { + @Override + public boolean test(Iterable iotas) { + var it = iotas.iterator(); + return it.hasNext() && first.test(it.next()) && it.hasNext() && second.test(it.next()) && !it.hasNext(); + } + } + record Any(IotaPredicate needs, IotaPredicate fallback) implements IotaMultiPredicate { + @Override + public boolean test(Iterable iotas) { + var ok = false; + for (var iota : iotas) { + if (needs.test(iota)) { + ok = true; + } else if (!fallback.test(iota)) { + return false; + } + } + return ok; + } + } + record All(IotaPredicate inner) implements IotaMultiPredicate { + @Override + public boolean test(Iterable iotas) { + for (var iota : iotas) { + if (!inner.test(iota)) { + return false; + } + } + return true; + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IotaPredicate.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IotaPredicate.java new file mode 100644 index 00000000..635869f9 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IotaPredicate.java @@ -0,0 +1,31 @@ +package at.petrak.hexcasting.api.casting.arithmetic; + +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; + +@FunctionalInterface +public interface IotaPredicate { + boolean test(Iota iota); + + static IotaPredicate or(IotaPredicate left, IotaPredicate right) { + return new Or(left, right); + } + + static IotaPredicate ofType(IotaType type) { + return new OfType(type); + } + + record Or(IotaPredicate left, IotaPredicate right) implements IotaPredicate { + @Override + public boolean test(Iota iota) { + return left.test(iota) || right.test(iota); + } + } + + record OfType(IotaType type) implements IotaPredicate { + @Override + public boolean test(Iota iota) { + return iota.getType().equals(this.type); + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java new file mode 100644 index 00000000..64ff91fb --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java @@ -0,0 +1,24 @@ +package at.petrak.hexcasting.api.casting.arithmetic; + +import java.util.Iterator; + +public record IterPair(T left, T right) implements Iterable { + @Override + public Iterator iterator() { + return new Iterator() { + int ix; + @Override + public boolean hasNext() { + return ix < 2; + } + @Override + public T next() { + switch (ix++) { + case 0: return left; + case 1: return right; + } + return null; + } + }; + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java new file mode 100644 index 00000000..7a898051 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java @@ -0,0 +1,74 @@ +package at.petrak.hexcasting.api.casting.arithmetic.engine; + +import at.petrak.hexcasting.api.casting.arithmetic.Arithmetic; +import at.petrak.hexcasting.api.casting.arithmetic.operator.Operator; +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class ArithmeticEngine { + private record OpCandidates(HexPattern pattern, int arity, List operators) { + public void addOp(Operator next) { + if (next.arity != arity) { + throw new IllegalArgumentException("Operators exist of differing arity!"); + } + operators.add(next); + } + } + + public final Arithmetic[] arithmetics; + private final Map operators = new HashMap<>(); + private final Map cache = new HashMap<>(); + + public ArithmeticEngine(Arithmetic... arithmetics) { + this.arithmetics = arithmetics; + for (var arith : arithmetics) { + for (var op : arith.opTypes()) { + operators.compute(op, ($, info) -> { + var operator = arith.getOperator(op); + if (info == null) { + info = new OpCandidates(op, operator.arity, new ArrayList<>()); + } + info.addOp(operator); + return info; + }); + } + } + } + + public Iterable operatorSyms() { + return operators.keySet(); + } + + public @Nullable Iota run(HexPattern operator, Stack iotas) { + var candidates = operators.get(operator); + if (candidates == null) + return null; // not an operator + HashCons hash = new HashCons.Pattern(operator); + var args = new ArrayList(candidates.arity()); + for (var i = 0; i < candidates.arity(); i++) { + if (iotas.isEmpty()) { + throw new IllegalStateException("Not enough args on stack for operator: " + operator); + } + var iota = iotas.pop(); + hash = new HashCons.Pair(iota.getType(), hash); + args.add(iota); + } + Collections.reverse(args); + var op = resolveCandidates(args, hash, candidates); + return op.apply(args); + } + + public Operator resolveCandidates(List args, HashCons hash, OpCandidates candidates) { + return cache.computeIfAbsent(hash, $ -> { + for (var op : candidates.operators()) { + if (op.accepts.test(args)) { + return op; + } + } + throw new IllegalArgumentException("No implementation candidates for op " + candidates.pattern() + " on args: " + args); + }); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java new file mode 100644 index 00000000..c7566e78 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java @@ -0,0 +1,12 @@ +package at.petrak.hexcasting.api.casting.arithmetic.engine; + + +import at.petrak.hexcasting.api.casting.iota.IotaType; +import at.petrak.hexcasting.api.casting.math.HexPattern; + +// Helper type for hashing lists of types. +public sealed interface HashCons { + public record Pattern(HexPattern operator) implements HashCons {} + + public record Pair(IotaType head, HashCons tail) implements HashCons {} +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/DoubleArithmetic.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/DoubleArithmetic.java new file mode 100644 index 00000000..1035c79d --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/DoubleArithmetic.java @@ -0,0 +1,69 @@ +package at.petrak.hexcasting.api.casting.arithmetic.impls; + +import at.petrak.hexcasting.api.casting.arithmetic.Arithmetic; +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.IotaPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.operator.Operator; +import at.petrak.hexcasting.api.casting.arithmetic.operator.OperatorBinary; +import at.petrak.hexcasting.api.casting.arithmetic.operator.OperatorUnary; +import at.petrak.hexcasting.api.casting.iota.DoubleIota; +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; + +import java.util.List; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleUnaryOperator; + +import static at.petrak.hexcasting.api.casting.arithmetic.operator.Operator.downcast; +import static at.petrak.hexcasting.common.lib.hex.HexIotaTypes.*; + +public enum DoubleArithmetic implements Arithmetic { + INSTANCE; + + public static final List OPS = List.of( + ADD, + SUB, + MUL, + DIV, + ABS, + POW, + FLOOR, + CEIL, + MOD + ); + + public static final IotaMultiPredicate ACCEPTS = IotaMultiPredicate.all(IotaPredicate.ofType(DOUBLE)); + + @Override + public String arithName() { + return "double_math"; + } + + @Override + public Iterable opTypes() { + return OPS; + } + + @Override + public Operator getOperator(HexPattern pattern) { + switch (pattern) { + case ADD: return make2(Double::sum); + case SUB: return make2((p, q) -> p - q); + case MUL: return make2((p, q) -> p * q); + case DIV: return make2((p, q) -> p / q); + case ABS: return make1(Math::abs); + case POW: return make2(Math::pow); + case FLOOR: return make1(Math::floor); + case CEIL: return make1(Math::ceil); + case MOD: return make2((p, q) -> p % q); + } + return null; + } + public static OperatorUnary make1(DoubleUnaryOperator op) { + return new OperatorUnary(ACCEPTS, i -> new DoubleIota(op.applyAsDouble(downcast(i, DOUBLE).getDouble()))); + } + public static OperatorBinary make2(DoubleBinaryOperator op) { + return new OperatorBinary(ACCEPTS, (i, j) -> new DoubleIota(op.applyAsDouble(downcast(i, DOUBLE).getDouble(), downcast(j, DOUBLE).getDouble()))); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/StringArithmetic.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/StringArithmetic.java new file mode 100644 index 00000000..4f6be300 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/StringArithmetic.java @@ -0,0 +1,47 @@ +package at.petrak.hexcasting.api.casting.arithmetic.impls; + +import at.petrak.hexcasting.api.casting.arithmetic.Arithmetic; +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.IotaPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.operator.Operator; +import at.petrak.hexcasting.api.casting.arithmetic.operator.OperatorBinary; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; + +import java.util.List; + +public enum StringArithmetic implements Arithmetic { + INSTANCE; + + public static final List OPS = List.of( + new ADD, + new MUL, + new ABS + ); + + @Override + public String arithName() { + return "string_math"; + } + + @Override + public Iterable opTypes() { + return OPS; + } + + @Override + public Operator getOperator(HexPattern pattern) { + switch (pattern) { + case ADD: return new OperatorBinary( + IotaMultiPredicate.all(IotaPredicate.ofType(HexIotaTypes.BOOLEAN)), + (p, q) -> new Iota(p.downcast(String.class) + q.downcast(String.class))); + case MUL: return new OperatorBinary( + IotaMultiPredicate.bofa(IotaPredicate.ofClass(String.class), IotaPredicate.ofClass(Double.class)), + (p, q) -> new Iota(new String(new char[q.downcast(Double.class).intValue()]).replace("\0", p.downcast(String.class)))); + case ABS: return new OperatorUnary( + IotaMultiPredicate.all(IotaPredicate.ofClass(String.class)), + s -> new Iota((double) s.downcast(String.class).length())); + } + return null; + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/Vec3Arithmetic.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/Vec3Arithmetic.java new file mode 100644 index 00000000..cb14098d --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/impls/Vec3Arithmetic.java @@ -0,0 +1,58 @@ +package at.petrak.hexcasting.api.casting.arithmetic.impls; + +import at.petrak.hexcasting.api.casting.arithmetic.Arithmetic; +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.IotaPredicate; +import at.petrak.hexcasting.api.casting.math.HexPattern; + +import java.util.ArrayList; +import java.util.List; + +import static at.petrak.hexcasting.common.lib.hex.HexIotaTypes.*; + +public enum Vec3Arithmetic implements Arithmetic { + INSTANCE; + + public static final List OPS; + static { + var ops = new ArrayList<>(DoubleArithmetic.OPS); + ops.add(PACK); + ops.add(UNPACK); + ops.remove(FLOOR); + ops.remove(CEIL); + OPS = ops; + } + + public static final IotaMultiPredicate ACCEPTS = IotaMultiPredicate.any(IotaPredicate.ofType(VEC3), IotaPredicate.ofType(DOUBLE)); + + @Override + public String arithName() { + return "vec3_math"; + } + + @Override + public Iterable opTypes() { + return OPS; + } + + @Override + public Operator getOperator(Symbol name) { + switch (name.inner()) { + case "pack": return OperatorPack.INSTANCE; + case "add": return make2(name, null); + case "sub": return make2(name, null); + case "mul": return make2(name, Vec3::dot); + case "div": return make2(name, Vec3::cross); + case "abs": return make1(Vec3::len); + case "pow": return make2(name, Vec3::proj); + case "mod": return make2(name, null); + } + return null; + } + public static OperatorUnary make1(Function op) { + return new OperatorUnary(ACCEPTS, i -> new Iota(op.apply(i.downcast(Vec3.class)))); + } + public static OperatorVec3Delegating make2(Symbol name, BiFunction op) { + return new OperatorVec3Delegating(op, name); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.java new file mode 100644 index 00000000..cd4e249c --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.java @@ -0,0 +1,26 @@ +package at.petrak.hexcasting.api.casting.arithmetic.operator; + +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; +import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota; + +public abstract class Operator { + public final int arity; + + public final IotaMultiPredicate accepts; + + public Operator(int arity, IotaMultiPredicate accepts) { + this.arity = arity; + this.accepts = accepts; + } + + public abstract Iota apply(Iterable iotas); + + @SuppressWarnings("unchecked") + public static T downcast(Iota iota, IotaType iotaType) { + if (iota.getType() != iotaType) + throw new IllegalStateException("Attempting to downcast " + iota + " to type: " + iotaType); + return (T) iota; + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java new file mode 100644 index 00000000..f5eb41b6 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java @@ -0,0 +1,22 @@ +package at.petrak.hexcasting.api.casting.arithmetic.operator; + + +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.iota.Iota; + +import java.util.function.BinaryOperator; + +public class OperatorBinary extends Operator { + public BinaryOperator inner; + + public OperatorBinary(IotaMultiPredicate accepts, BinaryOperator inner) { + super(2, accepts); + this.inner = inner; + } + + @Override + public Iota apply(Iterable iotas) { + var it = iotas.iterator(); + return inner.apply(it.next(), it.next()); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorPack.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorPack.java new file mode 100644 index 00000000..95dc3656 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorPack.java @@ -0,0 +1,27 @@ +package at.petrak.hexcasting.api.casting.arithmetic.operator; + + +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.IotaPredicate; +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.Vec3Iota; +import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import net.minecraft.world.phys.Vec3; + +public class OperatorPack extends Operator { + private OperatorPack() { + super(3, IotaMultiPredicate.all(IotaPredicate.ofType(HexIotaTypes.DOUBLE))); + } + + public static OperatorPack INSTANCE = new OperatorPack(); + + @Override + public Iota apply(Iterable iotas) { + var it = iotas.iterator(); + return new Vec3Iota(new Vec3( + downcast(it.next(), HexIotaTypes.DOUBLE).getDouble(), + downcast(it.next(), HexIotaTypes.DOUBLE).getDouble(), + downcast(it.next(), HexIotaTypes.DOUBLE).getDouble() + )); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java new file mode 100644 index 00000000..550848ee --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java @@ -0,0 +1,21 @@ +package at.petrak.hexcasting.api.casting.arithmetic.operator; + + +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.iota.Iota; + +import java.util.function.UnaryOperator; + +public class OperatorUnary extends Operator { + public UnaryOperator inner; + + public OperatorUnary(IotaMultiPredicate accepts, UnaryOperator inner) { + super(1, accepts); + this.inner = inner; + } + + @Override + public Iota apply(Iterable iotas) { + return inner.apply(iotas.iterator().next()); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorVec3Delegating.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorVec3Delegating.java new file mode 100644 index 00000000..e0bec5b6 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorVec3Delegating.java @@ -0,0 +1,47 @@ +package at.petrak.hexcasting.api.casting.arithmetic.operator; + +import at.petrak.hexcasting.api.casting.arithmetic.IotaMultiPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.IotaPredicate; +import at.petrak.hexcasting.api.casting.arithmetic.IterPair; +import at.petrak.hexcasting.api.casting.arithmetic.impls.DoubleArithmetic; +import at.petrak.hexcasting.api.casting.iota.DoubleIota; +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.Vec3Iota; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import kotlin.Pair; +import net.minecraft.world.phys.Vec3; + +import java.util.Objects; +import java.util.function.BiFunction; + +public class OperatorVec3Delegating extends Operator { + private final BiFunction op; + private final Operator fb; + public OperatorVec3Delegating(BiFunction core, HexPattern fallback) { + super(2, IotaMultiPredicate.any(IotaPredicate.ofType(HexIotaTypes.VEC3), IotaPredicate.ofType(HexIotaTypes.DOUBLE))); + op = core; + fb = Objects.requireNonNull(DoubleArithmetic.INSTANCE.getOperator(fallback)); + } + + @Override + public Iota apply(Iterable iotas) { + var it = iotas.iterator(); + var left = it.next(); + var right = it.next(); + if (op != null && left instanceof Vec3Iota lh && right instanceof Vec3Iota rh) { + return op.apply(lh.getVec3(), rh.getVec3()); + } + var lh = left instanceof Vec3Iota l ? l.getVec3() : triplicate(downcast(left, HexIotaTypes.DOUBLE).getDouble()); + var rh = right instanceof Vec3Iota r ? r.getVec3() : triplicate(downcast(right, HexIotaTypes.DOUBLE).getDouble()); + return new Vec3Iota(new Vec3( + downcast(fb.apply(new IterPair<>(new DoubleIota(lh.x()), new DoubleIota(rh.x()))), HexIotaTypes.DOUBLE).getDouble(), + downcast(fb.apply(new IterPair<>(new DoubleIota(lh.y()), new DoubleIota(rh.y()))), HexIotaTypes.DOUBLE).getDouble(), + downcast(fb.apply(new IterPair<>(new DoubleIota(lh.z()), new DoubleIota(rh.z()))), HexIotaTypes.DOUBLE).getDouble() + )); + } + + public static Vec3 triplicate(double in) { + return new Vec3(in, in, in); + } +}