001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.nbio.structure.contact;
022
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026import javax.vecmath.Point3d;
027import javax.vecmath.Vector3d;
028import java.io.Serializable;
029import java.util.Arrays;
030import java.util.Locale;
031import java.util.Objects;
032
033
034/**
035 * A bounding box for short cutting some geometrical calculations.
036 *
037 * See http://en.wikipedia.org/wiki/Bounding_volume
038 *
039 * @author Jose Duarte
040 *
041 */
042public class BoundingBox implements Serializable {
043
044        private static final long serialVersionUID = 1L;
045        private static final Logger logger = LoggerFactory.getLogger(StructureInterfaceList.class);
046
047
048        public double xmin;
049        public double xmax;
050        public double ymin;
051        public double ymax;
052        public double zmin;
053        public double zmax;
054
055        public BoundingBox(double xmin, double xmax, double ymin, double ymax, double zmin, double zmax) {
056                this.xmin = xmin;
057                this.xmax = xmax;
058                this.ymin = ymin;
059                this.ymax = ymax;
060                this.zmin = zmin;
061                this.zmax = zmax;
062        }
063
064        public BoundingBox(BoundingBox bb) {
065                this.xmin = bb.xmin;
066                this.xmax = bb.xmax;
067                this.ymin = bb.ymin;
068                this.ymax = bb.ymax;
069                this.zmin = bb.zmin;
070                this.zmax = bb.zmax;
071        }
072
073        /**
074         * Constructs a BoundingBox by calculating maxs and mins of given array of atoms.
075         * @param atoms the atom array
076         * @throws IllegalArgumentException if atom array is empty
077         * @throws NullPointerException if input is null
078         */
079        public BoundingBox (Point3d[] atoms) {
080
081                if (atoms.length==0)
082                        throw new IllegalArgumentException("Empty list of atoms is not allowed for BoundingBox construction");
083
084                xmax = atoms[0].x;
085                xmin = xmax;
086                ymax = atoms[0].y;
087                ymin = ymax;
088                zmax = atoms[0].z;
089                zmin = zmax;
090
091                for(int i=1;i<atoms.length;i++) {
092                        if(atoms[i].x > xmax) xmax = atoms[i].x;
093                        else if(atoms[i].x < xmin) xmin = atoms[i].x;
094
095                        if(atoms[i].y > ymax) ymax = atoms[i].y;
096                        else if(atoms[i].y < ymin) ymin = atoms[i].y;
097
098                        if(atoms[i].z > zmax) zmax = atoms[i].z;
099                        else if(atoms[i].z < zmin) zmin = atoms[i].z;
100                }
101
102        }
103
104        /**
105         * Given a set of bounding boxes returns a bounding box that bounds all of them.
106         * @param boxes an array of bounding boxes
107         * @throws IllegalArgumentException if input array is empty
108         * @throws NullPointerException if input is null
109         */
110        public BoundingBox(BoundingBox[] boxes) {
111
112                if (boxes.length==0)
113                        throw new IllegalArgumentException("Empty list of bounding boxes is not allowed for BoundingBox construction");
114
115                xmax = boxes[0].xmax;
116                xmin = boxes[0].xmin;
117                ymax = boxes[0].ymax;
118                ymin = boxes[0].ymin;
119                zmax = boxes[0].zmax;
120                zmin = boxes[0].zmin;
121
122                for (int i=1;i<boxes.length;i++) {
123                        if(boxes[i].xmax > xmax) xmax = boxes[i].xmax;
124                        else if(boxes[i].xmin < xmin) xmin = boxes[i].xmin;
125                        if(boxes[i].ymax > ymax) ymax = boxes[i].ymax;
126                        else if(boxes[i].ymin < ymin) ymin = boxes[i].ymin;
127                        if(boxes[i].zmax > zmax) zmax = boxes[i].zmax;
128                        else if(boxes[i].zmin < zmin) zmin = boxes[i].zmin;
129                }
130
131        }
132
133        private static class Bound implements Comparable<Bound> {
134                int cardinal;
135                double value;
136                public Bound(int cardinal,double value) {
137                        this.cardinal = cardinal;
138                        this.value = value;
139                }
140                @Override
141                public int compareTo(Bound o) {
142                        return Double.compare(this.value,o.value);
143                }
144                @Override
145                public String toString() {
146                        return "["+cardinal+","+value+"]";
147                }
148        }
149
150        /**
151         * Returns the dimensions of this bounding box.
152         *
153         * @return a double array (x,y,z) with the dimensions of the box.
154         */
155        public double[] getDimensions(){
156                double[] dim = new double[3];
157                dim[0] = xmax-xmin;
158                dim[1] = ymax-ymin;
159                dim[2] = zmax-zmin;
160                return dim;
161        }
162
163        /**
164         * Returns true if this bounding box overlaps given one, i.e. they are within
165         * one cutoff distance in one of their 3 dimensions.
166         * @param cutoff
167         * @return
168         */
169        public boolean overlaps(BoundingBox o, double cutoff) {
170                if (this==o) return true;
171                // x dimension
172                if (!areOverlapping(xmin,xmax,o.xmin,o.xmax,cutoff)) {
173                        return false;
174                }
175                // y dimension
176                if (!areOverlapping(ymin,ymax,o.ymin,o.ymax,cutoff)) {
177                        return false;
178                }
179                // z dimension
180                if (!areOverlapping(zmin,zmax,o.zmin,o.zmax,cutoff)) {
181                        return false;
182                }
183                return true;
184        }
185
186        private boolean areOverlapping(double imin, double imax, double jmin, double jmax, double cutoff) {
187
188                Bound[] bounds = {new Bound(0,imin), new Bound(1,imax),
189                                new Bound(2,jmin), new Bound(3,jmax)};
190
191                Arrays.sort(bounds);
192
193                if ((bounds[0].cardinal==0 && bounds[1].cardinal==1)) {
194                        if ((bounds[2].value-bounds[1].value)>cutoff) {
195                                return false;
196                        }
197                } else if (bounds[0].cardinal==2 && bounds[1].cardinal==3) {
198                        if ((bounds[2].value-bounds[1].value)>cutoff) {
199                                return false;
200                        }
201                }
202
203                return true;
204
205        }
206
207        /**
208         * Check if a given point falls within this box
209         * @param atom
210         * @return
211         */
212        public boolean contains(Point3d atom) {
213                double x = atom.x;
214                double y = atom.y;
215                double z = atom.z;
216                return xmin <= x && x <= xmax
217                                && ymin <= y && y <= ymax
218                                && zmin <= z && z <= zmax;
219        }
220
221        public void translate(Vector3d translation) {
222                xmin+=translation.x;
223                xmax+=translation.x;
224                ymin+=translation.y;
225                ymax+=translation.y;
226                zmin+=translation.z;
227                zmax+=translation.z;
228        }
229
230        /**
231         * Returns an array of size 2 with min and max values of given double array
232         * @param array
233         * @return
234         */
235        public double[] getMinMax(double[] array) {
236                double[] minmax = new double[2];
237
238                double max = Double.MIN_VALUE;
239                double min = Double.MAX_VALUE;
240
241                for(double value : array) {
242                        if(value > max) max = value;
243                        if(value < min) min = value;
244                }
245
246                minmax[0] = min;
247                minmax[1] = max;
248                return minmax;
249        }
250
251        @Override
252        public String toString() {
253                return String.format(Locale.US, "[(%7.2f,%7.2f),(%7.2f,%7.2f),(%7.2f,%7.2f)]", xmin,xmax,ymin,ymax,zmin,zmax);
254        }
255}