import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.PriorityQueue; import java.util.Scanner; class ColonySolver { public static void main(String[] args) { for(Colony colony : Colony.readColonies(new Scanner(System.in))) { System.out.printf("%.10f\n", colony.solve()); } } } public class Colony { public final static int MAX_N = 16; public final static int L = 100; public final static int HL = L/2; public final static double INF = 1e10; public static enum CROSS {CROSS_UP, CROSS_RIGHT}; private Vec3[] centers; private Vec3[] pointsSrcDst; private static Vec3 readVec3(Scanner s) { return new Vec3(s.nextInt(), s.nextInt(), s.nextInt()); } public Colony(Vec3[] _centers, Vec3 src, Vec3 dst) { centers = _centers; pointsSrcDst = new Vec3[]{src, dst}; } private Colony(Scanner s, int n) { centers = new Vec3[n]; for(int i = 0; i < n; i++) { centers[i] = readVec3(s); } pointsSrcDst = new Vec3[]{readVec3(s), readVec3(s)}; } public static ArrayList readColonies(Scanner s) { ArrayList output = new ArrayList(); while(true) { int n = s.nextInt(); if(n == 0) { return output; } output.add(new Colony(s, n)); } } private PointCostMap makePointCostMapOnlyNearFace(Point p) { PointCostMap map = new PointCostMap(); Face onTheFace = p.parent; for(Point nextPoint : onTheFace.points) { map.updateCost(nextPoint, p.distance(nextPoint)); } for(Edge e : onTheFace.edges) { for(Point nextPoint : e.nextEdge.parent.points) { if(p.distance(nextPoint) == 0.0) { map.updateCost(nextPoint, 0.0); } } } return map; } private static ArrayList> crossUpRightEdgeList(int miny, int minx, int maxy, int maxx) { ArrayList> list = new ArrayList>(); for(int y = HL; y < maxy; y += L) { double x = (double)((y - miny) * (maxx - minx)) / (maxy - miny) + minx; list.add(new DoubleAndObject(Math.hypot(y-miny, x-minx), CROSS.CROSS_UP)); } for(int x = HL; x < maxx; x += L) { double y = (double)((maxy - miny)*(x - minx)) / (maxx - minx) + miny; list.add(new DoubleAndObject(Math.hypot(y-miny, x-minx), CROSS.CROSS_RIGHT)); } Collections.sort(list); return list; } private PointCostMap makePointCostMap(Point startPoint, double upperBound){ PointCostMap map = makePointCostMapOnlyNearFace(startPoint); ArrayList listPoint2D = startPoint.isCornerPoint() ? startPoint.getDownLeftPoint2D() : startPoint.point2Ds; for(Point2D point : listPoint2D) { int sy = point.center.y; int sx = point.center.x; Face2D face = point.parent; for(int cy = HL; cy-sy < upperBound; cy += L) { for(int cx = HL; Math.hypot(cy-sy, cx-sx) < upperBound || cx < sx; cx += L) { map.updateCost(face.tripUpRightList(crossUpRightEdgeList(sy, sx, cy, cx)) .getUpRightPoint().original, Math.hypot(cy-sy, cx-sx)); } } } return map; } private double distanceSrcDst(Point src, Point dst, double upperBound) { for(Point2D src2D : src.point2Ds) { int sy = src2D.center.y; int sx = src2D.center.x; Face2D srcFace = src2D.parent; for(Point2D dst2D : dst.point2Ds) { Face2D dst2DExpected = dst2D.parent; for(int dy = dst2D.center.y; dy-sy < upperBound; dy += L) { for(int dx = dst2D.center.x; Math.hypot(sx-dx, sy-dy) < upperBound || dx < sx; dx += L) { if(dst2DExpected == srcFace.tripUpRightList(crossUpRightEdgeList(sy, sx, dy, dx))) { upperBound = Math.min(upperBound, Math.hypot(sx-dx, sy-dy)); } } } } } return upperBound; } private double distanceSrcDstWireFrame(Point src, Point dst) { PointCostMap costs = new PointCostMap(); PriorityQueue> queue = new PriorityQueue>(); HashSet visits = new HashSet(); queue.add(new DoubleAndObject(0.0, src)); while(!queue.isEmpty()) { DoubleAndObject dp = queue.poll(); double cost = dp.d; Point point = dp.t; if(visits.contains(point))continue; if(point == dst)return cost; for(Map.Entry me : makePointCostMapOnlyNearFace(point).entrySet()) { Point nextPoint = me.getKey(); double nextCost = me.getValue() + cost; if(costs.updateCost(nextPoint, nextCost)) { queue.add(new DoubleAndObject(nextCost, nextPoint)); } } } return INF; } public double solve(){ FaceGroup fg = new FaceGroup(centers, pointsSrcDst); Point src = fg.getPoint(pointsSrcDst[0]); Point dst = fg.getPoint(pointsSrcDst[1]); double upperBound = distanceSrcDstWireFrame(src, dst); if(upperBound == INF)return INF; upperBound = Math.min(upperBound, distanceSrcDst(src, dst, upperBound)); PointCostMap costs = new PointCostMap(); costs.updateCost(dst, upperBound); PointCostMap costsToDst = makePointCostMap(dst, upperBound); HashSet visits = new HashSet(); PriorityQueue> queue = new PriorityQueue>(); queue.add(new DoubleAndObject(0.0, src)); queue.add(new DoubleAndObject(upperBound, dst)); while(!queue.isEmpty()) { DoubleAndObject dp = queue.poll(); double cost = dp.d; Point point = dp.t; if(visits.contains(point))continue; visits.add(point); if(point == dst)return cost; double newGoalCost = cost + costsToDst.getCost(point); if(costs.updateCost(dst, newGoalCost)) { queue.add(new DoubleAndObject(newGoalCost, dst)); upperBound = newGoalCost; } for(Map.Entry me : makePointCostMap(point, upperBound - cost).entrySet()) { Point nextPoint = me.getKey(); double nextCost = me.getValue() + cost; if(costs.updateCost(nextPoint, nextCost)) { queue.add(new DoubleAndObject(nextCost, nextPoint)); } } } return INF; } } class DoubleAndObject implements Comparable>{ public Double d; public T t; public DoubleAndObject(Double _d, T _t) { d = _d; t = _t; } public int compareTo(DoubleAndObject dao) { return this.d.compareTo(dao.d); } } class Edge { private ArrayList edge2Ds; public Face parent; public Vec3 center; public Edge nextEdge; public Edge(Face _parent, Vec3 _center, Vec3 _direction) { parent = _parent; center = _center; edge2Ds = new ArrayList(); nextEdge = null; } public Vec3[] getConnectedFaceCenterList() { return new Vec3[]{center.add(parent.normal.scale(-Colony.HL)), center.add(center.sub(parent.center)), center.add(parent.normal.scale(Colony.HL))}; } public void moveParallel(Vec3 move) { center = center.add(move); } public void addEdge2D(Edge2D e) { edge2Ds.add(e); } private void setNextEdge(Edge _nextEdge) { nextEdge = _nextEdge; for(Edge2D edge2d : edge2Ds) { for(Edge2D nextEdge2d : nextEdge.edge2Ds) { if(edge2d.center.add(nextEdge2d.center).equals(Vec3.ZERO)) { edge2d.nextEdge = nextEdge2d; break; } } } } public void setNextFace(FaceGroup fg) { for(Vec3 c : getConnectedFaceCenterList()) { Face nextFace = fg.get(c); if(nextFace != null) { for(Edge nextEdge : nextFace.edges) { if(center.equals(nextEdge.center)) { setNextEdge(nextEdge); return; } } } } } } class Edge2D extends Object2D{ public Face2D parent; public Edge2D nextEdge; public Edge2D(Face2D _parent, Edge _original){ super(_original.center); parent = _parent; nextEdge = null; } } class Face { private final static Vec3 VEC3_X = new Vec3(1, 0, 0); private final static Vec3 VEC3_Y = new Vec3(0, 1, 0); private final static Vec3 VEC3_Z = new Vec3(0, 0, 1); public Vec3 center; public Vec3 normal; public ArrayList edges; public ArrayList points; public Face2D[] face2ds; protected Face(Vec3 _center, Vec3 _normal, Vec3[] pointsSrcDst) { center = new Vec3(0, 0, 0); normal = _normal; initEdgesAndPoints(); moveParallel(_center); for(Vec3 v : pointsSrcDst) { addIfOnFace(v); } face2ds = new Face2D[]{ new Face2D(this, 0), new Face2D(this, 1), new Face2D(this, 2), new Face2D(this, 3) }; } // assume center is (0, 0, 0) // because after generating, move whole object in parallel private void initEdgesAndPoints() { edges = new ArrayList(); points = new ArrayList(); Vec3 vec3ToEdge = !normal.outerProduct(VEC3_X).equals(Vec3.ZERO) ? VEC3_X : VEC3_Y; for(int i = 0; i < 4; i++) { Vec3 direction = normal.outerProduct(vec3ToEdge); edges.add(new Edge(this, vec3ToEdge.scale(Colony.HL), direction)); points.add(new Point(this, vec3ToEdge.add(direction).scale(Colony.HL))); vec3ToEdge = direction; } } public static ArrayList generateCubeFaces(Vec3 cubeCenter, Vec3[] pointsSrcDst) { ArrayList output = new ArrayList(); for(Vec3 u : Vec3.UNITS) { output.add(new Face(cubeCenter.add(u.scale(Colony.HL)), u, pointsSrcDst)); } return output; } private void moveParallel(Vec3 move) { center = center.add(move); for(Edge e : edges){ e.moveParallel(move); } for(Point p : points) { p.moveParallel(move); } } protected void addIfOnFace(Vec3 v) { if(center.diffMaxXYZ(v) < Colony.HL) { points.add(new Point(this, v)); } } } class Face2D { private final static Vec3 UNIT_ZPLUS = new Vec3(0, 0, 1); private final static Vec3 UP = new Vec3(0, Colony.HL, 0); private final static Vec3 RIGHT = new Vec3(Colony.HL, 0, 0); private final static Vec3 UP_RIGHT = new Vec3(Colony.HL, Colony.HL, 0); private Vec3 normal; private ArrayList edges; private ArrayList points; public Face2D(Face f, int countRotateZ) { normal = f.normal; edges = new ArrayList(); for(Edge edge : f.edges) { Edge2D edge2D = new Edge2D(this, edge); edges.add(edge2D); edge.addEdge2D(edge2D); } points = new ArrayList(); for(Point point : f.points) { Point2D point2D = new Point2D(this, point); points.add(point2D); point.addPoint2D(point2D); } move(f.center.scale(-1)); rotateNomalIsZPlus(); for(int i = 0; i < countRotateZ; i++) { rotate(Vec3.Rotate.ROTATE_Z); } } private void move(Vec3 v) { for(Edge2D e : edges) { e.move(v); } for(Point2D p : points) { p.move(v); } } private void rotateNomalIsZPlus() { for(int i = 0; i < 3; i++) { if(normal.equals(UNIT_ZPLUS))return; rotate(Vec3.Rotate.ROTATE_X); if(normal.equals(UNIT_ZPLUS))return; rotate(Vec3.Rotate.ROTATE_Y); } } private void rotate(Vec3.Rotate r) { normal = normal.rotate(r); for(Edge2D e : edges) { e.rotate(r); } for(Point2D p : points) { p.rotate(r); } } public Face2D tripUpRightList(ArrayList> arrayList) { Face2D now = this; for(DoubleAndObject daoc : arrayList) { Vec3 target = daoc.t == Colony.CROSS.CROSS_UP ? UP : RIGHT; for(Edge2D edge : now.edges) { if(edge.center.equals(target)) { now = edge.nextEdge.parent; break; } } } return now; } public Point2D getUpRightPoint() { for(Point2D p : points) { if(p.center.equals(UP_RIGHT))return p; } return null; } } class FaceGroup extends HashMap{ public FaceGroup(Vec3[] centers, Vec3[] pointsSrcDst){ super(); addAllFaces(centers, pointsSrcDst); addAllEdges(); } private void addAllEdges() { for(Map.Entry entry : entrySet()) { for(Edge edge : entry.getValue().edges) { edge.setNextFace(this); } } } private void addAllFaces(Vec3[] centers, Vec3[] pointsSrcDst) { for(Vec3 c : centers) { for(Face f : Face.generateCubeFaces(c, pointsSrcDst)) { addFace(f); } } } private void addFace(Face f) { Vec3 key = f.center; if(get(key) == null) { put(key, f); } else { remove(key); } } public Point getPoint(Vec3 v) { for(Map.Entry entry : entrySet()) { for(Point point : entry.getValue().points) { if(point.center.equals(v)) { return point; } } } return null; } } class Object2D { public Vec3 center; Object2D(Vec3 _center) { center = _center; } public void move(Vec3 v) { center = center.add(v); } public void rotate(Vec3.Rotate r) { center = center.rotate(r); } } class Point { public Face parent; public Vec3 center; public ArrayList point2Ds; private final static Vec3 DOWN_LEFT = new Vec3(-Colony.HL, -Colony.HL, 0); public Point(Face _parent, Vec3 _center) { parent = _parent; center = _center; point2Ds = new ArrayList(); } public void addPoint2D(Point2D p) { point2Ds.add(p); } public void moveParallel(Vec3 move) { center = center.add(move); } public double distance(Point p) { return center.sub(p.center).abs(); } public ArrayList getDownLeftPoint2D() { ArrayList output = new ArrayList(); for(Point2D p : point2Ds) { if(p.center.equals(DOWN_LEFT)) { output.add(p); } } return output; } public static boolean isCornerXYZ(int value) { return value % Colony.HL == 0 && (value / Colony.HL) % 2 != 0; } public boolean isCornerPoint() { return isCornerXYZ(center.x) && isCornerXYZ(center.y) && isCornerXYZ(center.z); } } class Point2D extends Object2D{ public Face2D parent; public Point original; public Point2D(Face2D _parent, Point _original){ super(_original.center); parent = _parent; original = _original; } } class PointCostMap extends HashMap{ public PointCostMap(){ super(); } public boolean updateCost(Point p, double newValue) { Double oldValue = get(p); if(oldValue == null || oldValue > newValue) { put(p, newValue); return true; } return false; } public double getCost(Point p) { Double value = get(p); return value == null ? Colony.INF : value; } } class Vec3 { public final static Vec3 ZERO = new Vec3(0, 0, 0); public final static Vec3[] UNITS = new Vec3[]{ new Vec3(1, 0, 0), new Vec3(-1, 0, 0), new Vec3(0, 1, 0), new Vec3( 0, -1, 0), new Vec3(0, 0, 1), new Vec3( 0, 0, -1)}; public static enum Rotate {ROTATE_X, ROTATE_Y, ROTATE_Z}; public int x; public int y; public int z; public Vec3(int _x, int _y, int _z) { x = _x; y = _y; z = _z; } public boolean equals(Object obj) { if(obj instanceof Vec3) { Vec3 v = (Vec3)obj; return this.x == v.x && this.y == v.y && this.z == v.z; } return false; } public int hashCode() { return x + 11*y + 19*z; } public Vec3 outerProduct(Vec3 v) { return new Vec3(this.y*v.z - this.z*v.y, this.z*v.x - this.x*v.z, this.x*v.y - this.y*v.x); } public Vec3 add(Vec3 v) { return new Vec3(this.x+v.x, this.y+v.y, this.z+v.z); } public Vec3 sub(Vec3 v) { return new Vec3(this.x-v.x, this.y-v.y, this.z-v.z); } public Vec3 scale(int s) { return new Vec3(this.x*s, this.y*s, this.z*s); } public int diffMaxXYZ(Vec3 v) { int output = 0; output = Math.max(output, Math.abs(this.x - v.x)); output = Math.max(output, Math.abs(this.y - v.y)); output = Math.max(output, Math.abs(this.z - v.z)); return output; } public boolean parallel(Vec3 v) { return ZERO.equals(this.outerProduct(v)); } public Vec3 rotate(Rotate r) { switch(r) { case ROTATE_X: return new Vec3(x, -z, y); case ROTATE_Y: return new Vec3(z, y, -x); default: return new Vec3(-y, x, z); } } public double abs() { return Math.sqrt(x*x + y*y + z*z); } }