Skip to content
Merged
13 changes: 13 additions & 0 deletions src/arcade/potts/env/location/PottsLocation.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import arcade.core.env.location.LocationContainer;
import arcade.core.util.Plane;
import arcade.core.util.Utilities;
import arcade.core.util.Vector;
import arcade.potts.util.PottsEnums.Direction;
import arcade.potts.util.PottsEnums.Region;
import static arcade.potts.util.PottsEnums.Direction;
import static arcade.potts.util.PottsEnums.Region;

Expand Down Expand Up @@ -605,6 +608,16 @@ void updateCenter(int x, int y, int z, int change) {
*/
abstract ArrayList<Voxel> getSelected(Voxel focus, double n);

/**
* Gets the voxel at specified percentage offsets along the location's axes with the provided
* ApicalAxis considered to be pointing up the Y axis.
*
* @param offsets the percent offsets along the location's axes
* @param apicalAxis the axis considered to be pointing up along the Y axis
* @return the voxel at the specified offset in the frame of the apical axis
*/
abstract Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis);

/**
* Gets the direction of the slice orthagonal to the direction with the smallest diameter.
*
Expand Down
56 changes: 56 additions & 0 deletions src/arcade/potts/env/location/PottsLocation2D.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package arcade.potts.env.location;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import arcade.core.util.Vector;
import static arcade.potts.util.PottsEnums.Direction;

/** Concrete implementation of {@link PottsLocation} for 2D. */
Expand Down Expand Up @@ -64,4 +67,57 @@ Direction getSlice(Direction direction, HashMap<Direction, Integer> diameters) {
ArrayList<Voxel> getSelected(Voxel focus, double n) {
return Location2D.getSelected(voxels, focus, n);
}

/**
* Gets the voxel at specified percentage offsets along the location's X and Y axes with the
* provided apicalAxis considered to be pointing up the Y axis. Returns null if this
* PottsLocation2D contains no voxels.
*
* @param offsets the percent offsets along the location's X and Y axes
* @param apicalAxis the axis considered to be pointing up along the Y axis
* @return the voxel through which the plane of division will pass
*/
@Override
public Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis) {
if (voxels.isEmpty()) {
return null;
}
if (offsets == null || offsets.size() != 2) {
throw new IllegalArgumentException("Offsets must be 2 integers.");
}

// Normalize axes
Vector yAxis = Vector.normalizeVector(apicalAxis);
Vector xAxis = Vector.normalizeVector(new Vector(apicalAxis.getY(), -apicalAxis.getX(), 0));

// Project voxels onto apical axis and group by rounded projection
HashMap<Integer, ArrayList<Voxel>> apicalBands = new HashMap<>();
ArrayList<Integer> apicalKeys = new ArrayList<>();

for (Voxel v : voxels) {
Vector pos = new Vector(v.x, v.y, 0);
double apicalProj = Vector.dotProduct(pos, yAxis);
int roundedProj = (int) Math.round(apicalProj);
apicalBands.computeIfAbsent(roundedProj, k -> new ArrayList<>()).add(v);
apicalKeys.add(roundedProj);
}

// Sort apical keys and choose percentile
Collections.sort(apicalKeys);
int yIndex =
Math.min(
apicalKeys.size() - 1,
(int) ((offsets.get(1) / 100.0) * apicalKeys.size()));
int targetApicalKey = apicalKeys.get(yIndex);

ArrayList<Voxel> band = apicalBands.get(targetApicalKey);
if (band == null || band.isEmpty()) {
return null;
}
// Project to orthogonal axis within the band and sort
band.sort(
Comparator.comparingDouble(v -> Vector.dotProduct(new Vector(v.x, v.y, 0), xAxis)));
int xIndex = Math.min(band.size() - 1, (int) ((offsets.get(0) / 100.0) * band.size()));
return band.get(xIndex);
}
}
8 changes: 8 additions & 0 deletions src/arcade/potts/env/location/PottsLocation3D.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.ArrayList;
import java.util.HashMap;
import arcade.core.util.Vector;
import arcade.potts.util.PottsEnums.Direction;
import static arcade.potts.util.PottsEnums.Direction;

/** Concrete implementation of {@link PottsLocation} for 3D. */
Expand Down Expand Up @@ -64,4 +66,10 @@ Direction getSlice(Direction direction, HashMap<Direction, Integer> diameters) {
ArrayList<Voxel> getSelected(Voxel focus, double n) {
return Location3D.getSelected(voxels, focus, n);
}

@Override
Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis) {
throw new UnsupportedOperationException(
"getOffsetInApicalFrame is not implemented for PottsLocation3D");
}
}
8 changes: 8 additions & 0 deletions src/arcade/potts/env/location/PottsLocations.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import ec.util.MersenneTwisterFast;
import arcade.core.env.location.Location;
import arcade.core.env.location.LocationContainer;
import arcade.core.util.Vector;
import arcade.potts.util.PottsEnums.Region;
import static arcade.potts.util.PottsEnums.Region;

/**
Expand Down Expand Up @@ -286,4 +288,10 @@ Location separateVoxels(

return splitLocation;
}

@Override
Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis) {
throw new UnsupportedOperationException(
"getOffsetInApicalFrame is not implemented for PottsLocations");
}
}
9 changes: 9 additions & 0 deletions src/arcade/potts/env/location/PottsLocations2D.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.ArrayList;
import java.util.HashMap;
import arcade.core.util.Vector;
import arcade.potts.util.PottsEnums.Direction;
import arcade.potts.util.PottsEnums.Region;
import static arcade.potts.util.PottsEnums.Direction;
import static arcade.potts.util.PottsEnums.Region;

Expand Down Expand Up @@ -70,4 +73,10 @@ Direction getSlice(Direction direction, HashMap<Direction, Integer> diameters) {
ArrayList<Voxel> getSelected(Voxel focus, double n) {
return Location2D.getSelected(locations.get(Region.DEFAULT).voxels, focus, n);
}

@Override
Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis) {
throw new UnsupportedOperationException(
"getOffsetInApicalFrame is not implemented for PottsLocations2D");
}
}
89 changes: 89 additions & 0 deletions test/arcade/potts/env/location/PottsLocation2DTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import ec.util.MersenneTwisterFast;
import arcade.core.util.Vector;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static arcade.potts.env.location.Voxel.VOXEL_COMPARATOR;
Expand Down Expand Up @@ -278,4 +279,92 @@ public void split_balanceableLocationRandomOne_returnsList() {
assertEquals(locVoxels, loc.voxels);
assertEquals(splitVoxels, split.voxels);
}

@Test
public void getOffsetInApicalFrame2D_offsetAtCenter_returnsExpectedVoxel() {
ArrayList<Voxel> voxels = new ArrayList<>();
// 3x3 grid centered at (0,0)
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
voxels.add(new Voxel(x, y, 0));
}
}
PottsLocation2D loc = new PottsLocation2D(voxels);

Vector apicalAxis = new Vector(0, 1, 0); // Y-axis
ArrayList<Integer> offsets = new ArrayList<>();
offsets.add(50); // middle of X axis
offsets.add(50); // middle of Y axis

Voxel result = loc.getOffsetInApicalFrame(offsets, apicalAxis);
assertEquals(new Voxel(0, 0, 0), result);
}

@Test
public void getOffsetInApicalFrame2D_offsetUpperRight_returnsExpectedVoxel() {
ArrayList<Voxel> voxels = new ArrayList<>();
for (int x = 0; x <= 4; x++) {
for (int y = 0; y <= 4; y++) {
voxels.add(new Voxel(x, y, 0));
}
}
PottsLocation2D loc = new PottsLocation2D(voxels);

Vector apicalAxis = new Vector(0, 1, 0); // Y-axis
ArrayList<Integer> offsets = new ArrayList<>();
offsets.add(100); // far right of X axis
offsets.add(100); // top of Y axis

Voxel result = loc.getOffsetInApicalFrame(offsets, apicalAxis);
assertEquals(new Voxel(4, 4, 0), result);
}

@Test
public void getOffsetInApicalFrame2D_emptyVoxels_returnsNull() {
PottsLocation2D loc = new PottsLocation2D(new ArrayList<>());

Vector apicalAxis = new Vector(1, 0, 0);
ArrayList<Integer> offsets = new ArrayList<>();
offsets.add(50);
offsets.add(50);

Voxel result = loc.getOffsetInApicalFrame(offsets, apicalAxis);
assertNull(result);
}

@Test
public void getOffsetInApicalFrame2D_invalidOffset_throwsException() {
ArrayList<Voxel> voxels = new ArrayList<>();
voxels.add(new Voxel(0, 0, 0));
PottsLocation2D loc = new PottsLocation2D(voxels);

Vector apicalAxis = new Vector(1, 0, 0);

ArrayList<Integer> badOffset = new ArrayList<>();
badOffset.add(50); // only one element

assertThrows(
IllegalArgumentException.class,
() -> {
loc.getOffsetInApicalFrame(badOffset, apicalAxis);
});
}

@Test
public void getOffsetInApicalFrame2D_nonOrthogonalAxis_returnsExpected() {
ArrayList<Voxel> voxels = new ArrayList<>();
voxels.add(new Voxel(0, 0, 0));
voxels.add(new Voxel(1, 1, 0));
voxels.add(new Voxel(2, 2, 0));
voxels.add(new Voxel(3, 3, 0));
PottsLocation2D loc = new PottsLocation2D(voxels);

Vector apicalAxis = new Vector(1, 1, 0); // diagonal
ArrayList<Integer> offsets = new ArrayList<>();
offsets.add(0); // lowest orthogonal axis
offsets.add(100); // farthest along apical

Voxel result = loc.getOffsetInApicalFrame(offsets, apicalAxis);
assertEquals(new Voxel(3, 3, 0), result);
}
}
13 changes: 13 additions & 0 deletions test/arcade/potts/env/location/PottsLocation3DTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import ec.util.MersenneTwisterFast;
import arcade.core.util.Vector;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static arcade.potts.env.location.Voxel.VOXEL_COMPARATOR;
Expand Down Expand Up @@ -298,4 +299,16 @@
assertEquals(locVoxels, loc.voxels);
assertEquals(splitVoxels, split.voxels);
}

@Test
public void getOffsetInApicalFrame_called_raisesUnsupportedOperationException() {
{

Check failure on line 305 in test/arcade/potts/env/location/PottsLocation3DTest.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] test/arcade/potts/env/location/PottsLocation3DTest.java#L305 <com.puppycrawl.tools.checkstyle.checks.blocks.AvoidNestedBlocksCheck>

Avoid nested blocks.
Raw output
/github/workspace/./test/arcade/potts/env/location/PottsLocation3DTest.java:305:9: error: Avoid nested blocks. (com.puppycrawl.tools.checkstyle.checks.blocks.AvoidNestedBlocksCheck)
PottsLocation3D loc = new PottsLocation3D(voxelListAB);
Vector apicalAxis = new Vector(0, 1, 0);
ArrayList<Integer> offsets = new ArrayList<>();
assertThrows(
UnsupportedOperationException.class,
() -> loc.getOffsetInApicalFrame(offsets, apicalAxis));
}
}
}
5 changes: 5 additions & 0 deletions test/arcade/potts/env/location/PottsLocationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ Direction getSlice(Direction direction, HashMap<Direction, Integer> diameters) {
ArrayList<Voxel> getSelected(Voxel center, double n) {
return new ArrayList<>();
}

@Override
Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis) {
return new Voxel(0, 0, 0);
}
}

@Test
Expand Down
14 changes: 14 additions & 0 deletions test/arcade/potts/env/location/PottsLocationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import ec.util.MersenneTwisterFast;
import arcade.core.util.Vector;
import arcade.potts.env.location.PottsLocationTest.PottsLocationMock;
import arcade.potts.util.PottsEnums.Direction;
import arcade.potts.util.PottsEnums.Region;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static arcade.core.ARCADETestUtilities.*;
Expand Down Expand Up @@ -963,4 +967,14 @@ public void separateVoxels_validListsWithRegions_updatesLists() {
assertEquals(2, split.locations.get(Region.DEFAULT).voxels.size());
assertEquals(2, split.locations.get(Region.DEFAULT).voxels.size());
}

@Test
public void getOffsetInApicalFrame_called_raisesUnsupportedOperationException() {
PottsLocationsMock loc = new PottsLocationsMock(voxelListForMultipleRegionsA);
Vector apicalAxis = new Vector(0, 1, 0);
ArrayList<Integer> offsets = new ArrayList<>();
assertThrows(
UnsupportedOperationException.class,
() -> loc.getOffsetInApicalFrame(offsets, apicalAxis));
}
}
Loading