Updates to the shape package

This commit is contained in:
Sollace 2022-10-08 10:57:16 +02:00
parent 645d6c6b7e
commit b8df7d0e67
19 changed files with 180 additions and 221 deletions

View file

@ -66,7 +66,7 @@ public class RainboomAbilitySpell extends AbstractSpell {
source.findAllEntitiesInRange(RADIUS).forEach(e -> { source.findAllEntitiesInRange(RADIUS).forEach(e -> {
e.damage(MagicalDamageSource.create("rainboom", source).setBreakSunglasses(), 6); e.damage(MagicalDamageSource.create("rainboom", source).setBreakSunglasses(), 6);
}); });
EFFECT_RANGE.offset(source.getOrigin()).getBlockPositions().forEach(pos -> { EFFECT_RANGE.translate(source.getOrigin()).getBlockPositions().forEach(pos -> {
BlockState state = source.getReferenceWorld().getBlockState(pos); BlockState state = source.getReferenceWorld().getBlockState(pos);
if (state.isIn(UTags.FRAGILE) && canBreak(pos, owner)) { if (state.isIn(UTags.FRAGILE) && canBreak(pos, owner)) {
owner.world.breakBlock(pos, true); owner.world.breakBlock(pos, true);

View file

@ -155,7 +155,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileSpell
if (radius > 2) { if (radius > 2) {
Vec3d origin = getOrigin(source); Vec3d origin = getOrigin(source);
new Sphere(false, radius).offset(origin).getBlockPositions().forEach(i -> { new Sphere(false, radius).translate(origin).getBlockPositions().forEach(i -> {
if (!canAffect(source, i)) { if (!canAffect(source, i)) {
return; return;
} }

View file

@ -66,7 +66,7 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileSpel
generateParticles(source); generateParticles(source);
} }
return new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))).offset(source.getOrigin()).getBlockPositions().reduce(false, return new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))).translate(source.getOrigin()).getBlockPositions().reduce(false,
(r, i) -> source.canModifyAt(i) && applyBlocks(source.getReferenceWorld(), i), (r, i) -> source.canModifyAt(i) && applyBlocks(source.getReferenceWorld(), i),
(a, b) -> a || b) (a, b) -> a || b)
|| applyEntities(null, source.getReferenceWorld(), source.getOriginVector()); || applyEntities(null, source.getReferenceWorld(), source.getOriginVector());

View file

@ -51,7 +51,7 @@ public class HydrophobicSpell extends AbstractSpell {
if (!source.isClient()) { if (!source.isClient()) {
World world = source.getReferenceWorld(); World world = source.getReferenceWorld();
Shape area = new Sphere(false, getRange(source)).offset(source.getOriginVector()); Shape area = new Sphere(false, getRange(source)).translate(source.getOriginVector());
storedFluidPositions.removeIf(entry -> { storedFluidPositions.removeIf(entry -> {
if (!area.isPointInside(Vec3d.ofCenter(entry.pos()))) { if (!area.isPointInside(Vec3d.ofCenter(entry.pos()))) {

View file

@ -42,7 +42,7 @@ public class IceSpell extends AbstractSpell {
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
boolean submerged = source.getEntity().isSubmergedInWater() || source.getEntity().isSubmergedIn(FluidTags.LAVA); boolean submerged = source.getEntity().isSubmergedInWater() || source.getEntity().isSubmergedIn(FluidTags.LAVA);
long blocksAffected = OUTER_RANGE.offset(source.getOrigin()).getBlockPositions().filter(i -> { long blocksAffected = OUTER_RANGE.translate(source.getOrigin()).getBlockPositions().filter(i -> {
if (source.canModifyAt(i) && applyBlockSingle(source.getEntity(), source.getReferenceWorld(), i, situation)) { if (source.canModifyAt(i) && applyBlockSingle(source.getEntity(), source.getReferenceWorld(), i, situation)) {
if (submerged & source.getOrigin().isWithinDistance(i, RADIUS - 1)) { if (submerged & source.getOrigin().isWithinDistance(i, RADIUS - 1)) {

View file

@ -43,7 +43,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
private float pitch; private float pitch;
private float yaw; private float yaw;
private PointGenerator particleArea = PARTICLE_AREA; private Shape particleArea = PARTICLE_AREA;
protected PortalSpell(CustomisedSpellType<?> type) { protected PortalSpell(CustomisedSpellType<?> type) {
super(type); super(type);

View file

@ -31,7 +31,7 @@ public class CloudsEscapingParticle extends GroundPoundParticle {
double columnHeight = 1 + age / 30; double columnHeight = 1 + age / 30;
new Sphere(true, columnHeight, 1, 1, 1) new Sphere(true, columnHeight, 1, 1, 1)
.offset(center) .translate(center)
.randomPoints(random) .randomPoints(random)
.forEach(point -> { .forEach(point -> {
ParticleUtils.spawnParticle(world, ParticleTypes.CLOUD, point, vel); ParticleUtils.spawnParticle(world, ParticleTypes.CLOUD, point, vel);

View file

@ -45,7 +45,7 @@ public class GroundPoundParticle extends Particle {
protected void spawnChildParticles() { protected void spawnChildParticles() {
Vec3d vel = new Vec3d(0, (0.5 + (Math.sin(age) * 2.5)) * 5, 0); Vec3d vel = new Vec3d(0, (0.5 + (Math.sin(age) * 2.5)) * 5, 0);
new Sphere(true, age, 1, 0, 1).offset(getPos()).randomPoints(random).forEach(point -> { new Sphere(true, age, 1, 0, 1).translate(getPos()).randomPoints(random).forEach(point -> {
BlockPos pos = new BlockPos(point).down(); BlockPos pos = new BlockPos(point).down();
BlockState state = world.getBlockState(pos); BlockState state = world.getBlockState(pos);

View file

@ -34,7 +34,7 @@ public interface ParticleSource extends ParticleSpawner {
} }
default void spawnParticles(Vec3d pos, PointGenerator area, int count, Consumer<Vec3d> particleSpawner) { default void spawnParticles(Vec3d pos, PointGenerator area, int count, Consumer<Vec3d> particleSpawner) {
area.offset(pos).randomPoints(count, getReferenceWorld().random).forEach(particleSpawner); area.translate(pos).randomPoints(count, getReferenceWorld().random).forEach(particleSpawner);
} }
@Override @Override

View file

@ -16,7 +16,7 @@ public interface ParticleUtils {
static PointGenerator getShapeFor(Entity entity) { static PointGenerator getShapeFor(Entity entity) {
final double halfDist = Math.abs(entity.getStandingEyeHeight() / 1.5); final double halfDist = Math.abs(entity.getStandingEyeHeight() / 1.5);
final double middle = entity.getBoundingBox().minY + halfDist; final double middle = entity.getBoundingBox().minY + halfDist;
return new Sphere(false, Math.abs((float)halfDist + entity.getWidth())).offset(new Vec3d(entity.getX(), middle, entity.getZ())); return new Sphere(false, Math.abs((float)halfDist + entity.getWidth())).translate(new Vec3d(entity.getX(), middle, entity.getZ()));
} }
static void spawnParticles(ParticleEffect effect, Entity entity, int count) { static void spawnParticles(ParticleEffect effect, Entity entity, int count) {
@ -24,7 +24,7 @@ public interface ParticleUtils {
} }
static void spawnParticles(ParticleEffect effect, World world, Vec3d origin, int count) { static void spawnParticles(ParticleEffect effect, World world, Vec3d origin, int count) {
spawnParticles(world, Sphere.UNIT_SPHERE.offset(origin), effect, count); spawnParticles(world, Sphere.UNIT_SPHERE.translate(origin), effect, count);
} }
static void spawnParticles(World world, PointGenerator points, ParticleEffect effect, int count) { static void spawnParticles(World world, PointGenerator points, ParticleEffect effect, int count) {

View file

@ -12,6 +12,10 @@ import net.minecraft.world.EntityView;
public interface VecHelper { public interface VecHelper {
static Vec3d divide(Vec3d one, Vec3d two) {
return new Vec3d(one.x / two.x, one.y / two.y, one.z / two.z);
}
static Vec3d supply(DoubleSupplier rng) { static Vec3d supply(DoubleSupplier rng) {
return new Vec3d(rng.getAsDouble(), rng.getAsDouble(), rng.getAsDouble()); return new Vec3d(rng.getAsDouble(), rng.getAsDouble(), rng.getAsDouble());
} }

View file

@ -49,20 +49,21 @@ public class Cylinder implements Shape {
} }
@Override @Override
public double getVolumeOfSpawnableSpace() { public double getVolume() {
return volume; return volume;
} }
private double computeSpawnableSpace() { private double computeSpawnableSpace() {
if (hollow) { if (!hollow) {
if (stretchX != stretchZ) { return Math.PI * (stretchX * rad * stretchZ * rad) * height;
double result = 3 * (stretchX + stretchZ);
result -= Math.sqrt((10 * stretchX * stretchZ) + 3 * ((stretchX * stretchX) + (stretchZ * stretchZ)));
return Math.PI * result;
}
return 2 * Math.PI * rad * stretchX * height;
} }
return Math.PI * (stretchX * rad * stretchZ * rad) * height;
if (stretchX != stretchZ) {
double result = 3 * (stretchX + stretchZ);
result -= Math.sqrt((10 * stretchX * stretchZ) + 3 * ((stretchX * stretchX) + (stretchZ * stretchZ)));
return Math.PI * result;
}
return 2 * Math.PI * rad * stretchX * height;
} }
@Override @Override

View file

@ -1,84 +1,54 @@
package com.minelittlepony.unicopia.util.shape; package com.minelittlepony.unicopia.util.shape;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
/** /**
* A lonely Line. The simplest form of shape. * A lonely Line. The simplest form of shape.
*
*/ */
public class Line implements Shape { public class Line implements Shape {
private final double len; private final double length;
private final double dX; private final Vec3d gradient;
private final double dY;
private final double dZ;
private final double sX;
private final double sY;
private final double sZ;
/**
* Creates a line with a given length, starting point, and gradient represented
* by another point.
*
* @param length Length of this line
* @param startX Offset X from origin
* @param startY Offset Y from origin
* @param startZ Offset Z from origin
* @param deltaX Change in X
* @param deltaY Change in Y
* @param deltaZ Change in Z
*/
public Line(double length, double startX, double startY, double startZ, double deltaX, double deltaY, double deltaZ) {
len = length;
dX = deltaX;
dY = deltaY;
dZ = deltaZ;
sX = startX;
sY = startY;
sZ = startZ;
}
public Line(Vec3d start, Vec3d end) { public Line(Vec3d start, Vec3d end) {
Vec3d lenV = end.subtract(start); Vec3d lenV = end.subtract(start);
length = lenV.length();
this.gradient = lenV.multiply(1/length);
}
len = lenV.length(); public Line(double length, Vec3d gradient) {
this.length = length;
sX = start.x; this.gradient = gradient.normalize();
sY = start.y;
sZ = start.z;
dX = lenV.x / len;
dY = lenV.y / len;
dZ = lenV.z / len;
} }
@Override @Override
public double getVolumeOfSpawnableSpace() { public double getVolume() {
return len; return length;
} }
@Override @Override
public Vec3d computePoint(Random rand) { public Vec3d computePoint(Random rand) {
double distance = MathHelper.nextDouble(rand, 0, len); return gradient.multiply(MathHelper.nextDouble(rand, 0, length));
return new Vec3d(dX, dY, dZ).multiply(distance).add(sX, sY, sZ);
} }
@Override @Override
public boolean isPointInside(Vec3d point) { public boolean isPointInside(Vec3d point) {
return point.x/dX == point.y/dY && point.x/dX == point.z/dZ; Vec3d divided = VecHelper.divide(point, gradient);
return divided.x == divided.y && divided.x == divided.z;
} }
@Override @Override
public Vec3d getLowerBound() { public Vec3d getLowerBound() {
return new Vec3d(sX, sY, sZ); return Vec3d.ZERO;
} }
@Override @Override
public Vec3d getUpperBound() { public Vec3d getUpperBound() {
return new Vec3d(sX + dX, sY + dY, sZ + dZ).multiply(len); return gradient.multiply(length);
} }
} }

View file

@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.util.shape;
import java.util.Spliterator; import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator; import java.util.Spliterators.AbstractSpliterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -15,35 +14,21 @@ import net.minecraft.util.math.random.Random;
*Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for).
*/ */
public interface PointGenerator { public interface PointGenerator {
/**
* Get the volume of space filled by this shape, or the surface area if hollow.
*
* @return double volume
*/
double getVolumeOfSpawnableSpace();
/** /**
* Computes a random coordinate that falls within this shape's designated area. * Computes a random coordinate that falls within this shape's designated area.
*/ */
Vec3d computePoint(Random rand); Vec3d computePoint(Random rand);
/**
* Returns a sequence of random points dealed out to uniformly fill this shape's area.
*/
default Stream<Vec3d> randomPoints(Random rand) {
return randomPoints((int)getVolumeOfSpawnableSpace(), rand);
}
/** /**
* Returns a sequence of N random points. * Returns a sequence of N random points.
*/ */
default Stream<Vec3d> randomPoints(int n, Random rand) { default Stream<Vec3d> randomPoints(int n, Random rand) {
AtomicInteger atom = new AtomicInteger(n);
return StreamSupport.stream(new AbstractSpliterator<Vec3d>(n, Spliterator.SIZED) { return StreamSupport.stream(new AbstractSpliterator<Vec3d>(n, Spliterator.SIZED) {
private int index = n;
@Override @Override
public boolean tryAdvance(Consumer<? super Vec3d> consumer) { public boolean tryAdvance(Consumer<? super Vec3d> consumer) {
if (--index >= 0) {
if (atom.decrementAndGet() >= 0) {
consumer.accept(computePoint(rand)); consumer.accept(computePoint(rand));
return true; return true;
} }
@ -54,37 +39,17 @@ public interface PointGenerator {
} }
/** /**
* Returns a stream of block positions. * Returns a new shape with after applying additional rotation.
*/ */
Stream<BlockPos> getBlockPositions(); default PointGenerator rotate(float pitch, float yaw) {
return pitch == 0 && yaw == 0 ? this : new RotatedPointGenerator(this, pitch, yaw);
/**
* Returns a new point generator where all of its points are offset by the specified amount.
*/
default PointGenerator offset(Vec3i offset) {
return offset(Vec3d.of(offset));
} }
/** default PointGenerator translate(Vec3i offset) {
* Returns a new point generator where all of its points are offset by the specified amount. return offset.equals(Vec3i.ZERO) ? this : translate(Vec3d.of(offset));
*/ }
default PointGenerator offset(Vec3d offset) {
final PointGenerator source = this;
return new PointGenerator() {
@Override
public double getVolumeOfSpawnableSpace() {
return source.getVolumeOfSpawnableSpace();
}
@Override default PointGenerator translate(Vec3d offset) {
public Vec3d computePoint(Random rand) { return offset.equals(Vec3d.ZERO) ? this : new TranslatedPointGenerator(this, offset);
return source.computePoint(rand).add(offset);
}
@Override
public Stream<BlockPos> getBlockPositions() {
return source.getBlockPositions().map(pos -> pos.add(offset.x, offset.y, offset.z));
}
};
} }
} }

View file

@ -0,0 +1,48 @@
package com.minelittlepony.unicopia.util.shape;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
record RotatedPointGenerator (
PointGenerator original,
float pitch,
float yaw
) implements Shape {
@Override
public double getVolume() {
return ((Shape)original).getVolume();
}
@Override
public Vec3d computePoint(Random rand) {
return rotate(original.computePoint(rand));
}
@Override
public Vec3d getLowerBound() {
return rotate(((Shape)original).getLowerBound());
}
@Override
public Vec3d getUpperBound() {
return rotate(((Shape)original).getUpperBound());
}
@Override
public boolean isPointInside(Vec3d point) {
return ((Shape)original).isPointInside(point.rotateY(-yaw).rotateX(-pitch));
}
private Vec3d rotate(Vec3d vec) {
return vec.rotateX(pitch).rotateY(yaw);
}
@Override
public Shape rotate(float pitch, float yaw) {
if (pitch == 0 && yaw == 0) {
return this;
}
return new RotatedPointGenerator(this, this.pitch + pitch, this.yaw + yaw);
}
}

View file

@ -1,51 +0,0 @@
package com.minelittlepony.unicopia.util.shape;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
final class RotatedShape implements Shape {
private final float pitch;
private final float yaw;
private final Shape original;
RotatedShape(Shape original, float pitch, float yaw) {
this.original = original;
this.pitch = pitch;
this.yaw = yaw;
}
@Override
public double getVolumeOfSpawnableSpace() {
return original.getVolumeOfSpawnableSpace();
}
@Override
public Vec3d computePoint(Random rand) {
return original.computePoint(rand).rotateX(pitch).rotateY(yaw);
}
@Override
public Vec3d getLowerBound() {
return original.getLowerBound().rotateX(pitch).rotateY(yaw);
}
@Override
public Vec3d getUpperBound() {
return original.getUpperBound().rotateX(pitch).rotateY(yaw);
}
@Override
public boolean isPointInside(Vec3d point) {
return original.isPointInside(point.rotateY(-yaw).rotateX(-pitch));
}
@Override
public Shape rotate(float pitch, float yaw) {
if (pitch == 0 && yaw == 0) {
return this;
}
return new RotatedShape(this, this.pitch + pitch, this.yaw + yaw);
}
}

View file

@ -10,6 +10,13 @@ import net.minecraft.util.math.random.Random;
*Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for).
*/ */
public interface Shape extends PointGenerator { public interface Shape extends PointGenerator {
/**
* Get the volume of space filled by this shape, or the surface area if hollow.
*
* @return double volume
*/
double getVolume();
/** /**
* Gets the lower bounds of the region occupied by this shape. * Gets the lower bounds of the region occupied by this shape.
*/ */
@ -28,7 +35,6 @@ public interface Shape extends PointGenerator {
/** /**
* Returns a stream of all block positions that fit inside this shape. * Returns a stream of all block positions that fit inside this shape.
*/ */
@Override
default Stream<BlockPos> getBlockPositions() { default Stream<BlockPos> getBlockPositions() {
return BlockPos.stream( return BlockPos.stream(
new BlockPos(getLowerBound()), new BlockPos(getLowerBound()),
@ -36,60 +42,28 @@ public interface Shape extends PointGenerator {
).filter(pos -> isPointInside(Vec3d.ofCenter(pos))); ).filter(pos -> isPointInside(Vec3d.ofCenter(pos)));
} }
/**
* Returns a sequence of random points dealed out to uniformly fill this shape's area.
*/
default Stream<Vec3d> randomPoints(Random rand) {
return randomPoints((int)getVolume(), rand);
}
/** /**
* Returns a new shape with after applying additional rotation. * Returns a new shape with after applying additional rotation.
*/ */
@Override
default Shape rotate(float pitch, float yaw) { default Shape rotate(float pitch, float yaw) {
if (pitch == 0 && yaw == 0) { return pitch == 0 && yaw == 0 ? this : new RotatedPointGenerator(this, pitch, yaw);
return this;
}
return new RotatedShape(this, pitch, yaw);
} }
/**
* Returns a new point generator where all of its points are offset by the specified amount.
*/
@Override @Override
default Shape offset(Vec3i offset) { default Shape translate(Vec3i offset) {
return offset(Vec3d.of(offset)); return offset.equals(Vec3i.ZERO) ? this : translate(Vec3d.of(offset));
} }
/**
* Returns a new point generator where all of its points are offset by the specified amount.
*/
@Override @Override
default Shape offset(Vec3d offset) { default Shape translate(Vec3d offset) {
final Shape source = this; return offset.equals(Vec3d.ZERO) ? this : new TranslatedPointGenerator(this, offset);
return new Shape() {
@Override
public double getVolumeOfSpawnableSpace() {
return source.getVolumeOfSpawnableSpace();
}
@Override
public Vec3d computePoint(Random rand) {
return source.computePoint(rand).add(offset);
}
@Override
public Stream<BlockPos> getBlockPositions() {
return source.getBlockPositions().map(pos -> pos.add(offset.x, offset.y, offset.z));
}
@Override
public Vec3d getLowerBound() {
return source.getLowerBound().add(offset);
}
@Override
public Vec3d getUpperBound() {
return source.getLowerBound().add(offset);
}
@Override
public boolean isPointInside(Vec3d point) {
return source.isPointInside(point.subtract(offset));
}
};
} }
} }

View file

@ -48,19 +48,20 @@ public class Sphere implements Shape {
} }
@Override @Override
public double getVolumeOfSpawnableSpace() { public double getVolume() {
return volume; return volume;
} }
private double computeSpawnableSpace() { private double computeSpawnableSpace() {
if (hollow) { if (!hollow) {
if (stretch.x == stretch.y && stretch.y == stretch.z) { return computeEllipsoidVolume(rad, stretch);
double radius = rad * stretch.x;
return 4 * Math.PI * radius * radius;
}
return computeEllipsoidArea(rad, stretch);
} }
return computeEllipsoidVolume(rad, stretch);
if (stretch.x == stretch.y && stretch.y == stretch.z) {
double radius = rad * stretch.x;
return 4 * Math.PI * radius * radius;
}
return computeEllipsoidArea(rad, stretch);
} }
@Override @Override
@ -75,7 +76,11 @@ public class Sphere implements Shape {
double phi = MathHelper.nextDouble(rand, 0, Math.PI * 2); double phi = MathHelper.nextDouble(rand, 0, Math.PI * 2);
double theta = Math.asin(z / rad); double theta = Math.asin(z / rad);
return new Vec3d(rad * Math.cos(theta) * Math.cos(phi) * stretch.x, rad * Math.cos(theta) * Math.sin(phi) * stretch.y, z * stretch.z); return new Vec3d(
rad * Math.cos(theta) * Math.cos(phi) * stretch.x,
rad * Math.cos(theta) * Math.sin(phi) * stretch.y,
z * stretch.z
);
} }
@Override @Override
@ -89,12 +94,12 @@ public class Sphere implements Shape {
@Override @Override
public Vec3d getLowerBound() { public Vec3d getLowerBound() {
return new Vec3d(-rad * stretch.x, -rad * stretch.y, -rad * stretch.z); return stretch.multiply(-rad);
} }
@Override @Override
public Vec3d getUpperBound() { public Vec3d getUpperBound() {
return new Vec3d(rad * stretch.x, rad * stretch.y, rad * stretch.z); return stretch.multiply(rad);
} }
public static double computeEllipsoidArea(double rad, Vec3d stretch) { public static double computeEllipsoidArea(double rad, Vec3d stretch) {

View file

@ -0,0 +1,43 @@
package com.minelittlepony.unicopia.util.shape;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
record TranslatedPointGenerator (
PointGenerator source,
Vec3d offset
) implements Shape {
@Override
public double getVolume() {
return ((Shape)source).getVolume();
}
@Override
public Vec3d computePoint(Random rand) {
return source.computePoint(rand).add(offset);
}
@Override
public Vec3d getLowerBound() {
return ((Shape)source).getLowerBound().add(offset);
}
@Override
public Vec3d getUpperBound() {
return ((Shape)source).getUpperBound().add(offset);
}
@Override
public boolean isPointInside(Vec3d point) {
return ((Shape)source).isPointInside(point.subtract(offset));
}
@Override
public Shape translate(Vec3d offset) {
if (offset.equals(Vec3d.ZERO)) {
return this;
}
return new TranslatedPointGenerator(source, this.offset.add(offset));
}
}