|
1 | 1 | package arcade.potts.env.location; |
2 | 2 |
|
3 | 3 | import java.util.ArrayList; |
| 4 | +import java.util.Collections; |
| 5 | +import java.util.Comparator; |
4 | 6 | import java.util.HashMap; |
| 7 | +import arcade.core.util.Vector; |
5 | 8 | import static arcade.potts.util.PottsEnums.Direction; |
6 | 9 |
|
7 | 10 | /** Concrete implementation of {@link PottsLocation} for 2D. */ |
@@ -64,4 +67,57 @@ Direction getSlice(Direction direction, HashMap<Direction, Integer> diameters) { |
64 | 67 | ArrayList<Voxel> getSelected(Voxel focus, double n) { |
65 | 68 | return Location2D.getSelected(voxels, focus, n); |
66 | 69 | } |
| 70 | + |
| 71 | + /** |
| 72 | + * Gets the voxel at specified percentage offsets along the location's X and Y axes with the |
| 73 | + * provided apicalAxis considered to be pointing up the Y axis. Returns null if this |
| 74 | + * PottsLocation2D contains no voxels. |
| 75 | + * |
| 76 | + * @param offsets the percent offsets along the location's X and Y axes |
| 77 | + * @param apicalAxis the axis considered to be pointing up along the Y axis |
| 78 | + * @return the voxel through which the plane of division will pass |
| 79 | + */ |
| 80 | + @Override |
| 81 | + public Voxel getOffsetInApicalFrame(ArrayList<Integer> offsets, Vector apicalAxis) { |
| 82 | + if (voxels.isEmpty()) { |
| 83 | + return null; |
| 84 | + } |
| 85 | + if (offsets == null || offsets.size() != 2) { |
| 86 | + throw new IllegalArgumentException("Offsets must be 2 integers."); |
| 87 | + } |
| 88 | + |
| 89 | + // Normalize axes |
| 90 | + Vector yAxis = Vector.normalizeVector(apicalAxis); |
| 91 | + Vector xAxis = Vector.normalizeVector(new Vector(apicalAxis.getY(), -apicalAxis.getX(), 0)); |
| 92 | + |
| 93 | + // Project voxels onto apical axis and group by rounded projection |
| 94 | + HashMap<Integer, ArrayList<Voxel>> apicalBands = new HashMap<>(); |
| 95 | + ArrayList<Integer> apicalKeys = new ArrayList<>(); |
| 96 | + |
| 97 | + for (Voxel v : voxels) { |
| 98 | + Vector pos = new Vector(v.x, v.y, 0); |
| 99 | + double apicalProj = Vector.dotProduct(pos, yAxis); |
| 100 | + int roundedProj = (int) Math.round(apicalProj); |
| 101 | + apicalBands.computeIfAbsent(roundedProj, k -> new ArrayList<>()).add(v); |
| 102 | + apicalKeys.add(roundedProj); |
| 103 | + } |
| 104 | + |
| 105 | + // Sort apical keys and choose percentile |
| 106 | + Collections.sort(apicalKeys); |
| 107 | + int yIndex = |
| 108 | + Math.min( |
| 109 | + apicalKeys.size() - 1, |
| 110 | + (int) ((offsets.get(1) / 100.0) * apicalKeys.size())); |
| 111 | + int targetApicalKey = apicalKeys.get(yIndex); |
| 112 | + |
| 113 | + ArrayList<Voxel> band = apicalBands.get(targetApicalKey); |
| 114 | + if (band == null || band.isEmpty()) { |
| 115 | + return null; |
| 116 | + } |
| 117 | + // Project to orthogonal axis within the band and sort |
| 118 | + band.sort( |
| 119 | + Comparator.comparingDouble(v -> Vector.dotProduct(new Vector(v.x, v.y, 0), xAxis))); |
| 120 | + int xIndex = Math.min(band.size() - 1, (int) ((offsets.get(0) / 100.0) * band.size())); |
| 121 | + return band.get(xIndex); |
| 122 | + } |
67 | 123 | } |
0 commit comments