def add(self, item): if self.depth < 8 and self.ne == None: cx = self.bbox.center.x cy = self.bbox.center.y self.ne = QuadNode( self.depth + 1, BoundingBox(cx, cy, self.bbox.maxX, self.bbox.maxY)) self.se = QuadNode( self.depth + 1, BoundingBox(cx, self.bbox.minY, self.bbox.maxX, cy)) self.sw = QuadNode( self.depth + 1, BoundingBox(self.bbox.minX, self.bbox.minY, cx, cy)) self.nw = QuadNode( self.depth + 1, BoundingBox(self.bbox.minX, cy, cx, self.bbox.maxY)) if self.depth == 8: self.items.append(item) return if self.ne.bbox.overlaps(item.bbox): self.ne.add(item) if self.se.bbox.overlaps(item.bbox): self.se.add(item) if self.sw.bbox.overlaps(item.bbox): self.sw.add(item) if self.nw.bbox.overlaps(item.bbox): self.nw.add(item)
def get_bb(fp, frame, toolName): undo_pos = fp.tell() line = fp.readline() # create default invalid bbox (aibu format for no bbx frames) no_annot_bb = BoundingBox("", "", frame) # aibu if toolName == config.Tool.AIBU: # no need for line check return BoundingBox(line, toolName, frame) # darklabel if toolName == config.Tool.DARKLABEL: parts = line.split(",") if parts[0] == str(frame): return BoundingBox(line, toolName) # vatic if toolName == config.Tool.VATIC: if "<annotation>" in line: undo_pos = fp.tell() line = fp.readline() if "<object>" in line: undo_pos = fp.tell() line = fp.readline() if "<t>" + str(frame) + "</t>" in line: return BoundingBox(line, toolName) # wrong framenumber or other error fp.seek(undo_pos) # undo last read return no_annot_bb
def _extract_bounding_boxes(self, gt, ignore_grade: bool=False): boundingBoxes = BoundingBoxes() # gt boxes for image_id in gt.images: for x_min, y_min, x_max, y_max, class_id in gt.images[image_id].labels: if ignore_grade: class_id = 1 temp = BoundingBox(imageName=image_id, classId=class_id, x=max(x_min, 0), y=max(y_min, 0), w=min(256, x_max - x_min), h=min(256, y_max - y_min), typeCoordinates=CoordinatesType.Absolute, bbType=BBType.GroundTruth, format=BBFormat.XYWH, imgSize=(256, 256)) boundingBoxes.addBoundingBox(temp) for x_min, y_min, x_max, y_max, class_id in self.images[image_id].labels: if ignore_grade: class_id = 1 temp = BoundingBox(imageName=image_id, classId=class_id, x=max(x_min, 0), y=max(y_min, 0), w=min(256, x_max - x_min), h=min(256, y_max - y_min), typeCoordinates=CoordinatesType.Absolute, classConfidence=1, bbType=BBType.Detected, format=BBFormat.XYWH, imgSize=(256, 256)) boundingBoxes.addBoundingBox(temp) return boundingBoxes
def get_all_bbox(gt_json, det_json): allBoundingBoxes = BoundingBoxes() det = json.load(open(det_json,'r')) for k, v in det.items(): nameOfImage = k for obj in v: idClass = obj[5] # class confidence = float(obj[4]) # confidence x1 = float(obj[0]) y1 = float(obj[1]) x2 = float(obj[2]) y2 = float(obj[3]) bb = BoundingBox(nameOfImage, idClass, x1, y1, x2, y2, bbType=BBType.Detected, classConfidence=confidence, format=BBFormat.XYX2Y2) allBoundingBoxes.addBoundingBox(bb) gt = pd.read_json(gt_json, lines=True) for i in range(gt.shape[0]): url = gt.loc[i]['url'] filename = url.split('/')[-1] if filename in imgs: if len(gt.loc[i]['label'][0]['data']) > 0: for ix, data in enumerate(gt.loc[i]['label'][0]['data']): x1, y1 = data['bbox'][0] x2, y2 = data['bbox'][2] idClass = data['class'] bb = BoundingBox(filename, idClass,x1,y1,x2,y2, bbType=BBType.GroundTruth, classConfidence=confidence, format=BBFormat.XYX2Y2) allBoundingBoxes.addBoundingBox(bb) return allBoundingBoxes
def default_value(nameOfImage, idClass, coordType, imgSize, isGT, bbFormat): idClass = '0' # class x = 0 #x_topleft y = 0 #y_topleft w = 0 h = 0 confidence = 0 if isGT: bb = BoundingBox(nameOfImage, idClass, x, y, w, h, coordType, imgSize, BBType.GroundTruth, format=bbFormat) else: bb = BoundingBox(nameOfImage, idClass, x, y, w, h, coordType, imgSize, BBType.Detected, confidence, format=bbFormat) return bb
def testGetBoundingBox(self): validFile = 'tests/WLBG-geog.csv' presPts = PresencePoints(validFile, 'WLBG') bbox = BoundingBox(presPts.points, presPts.epsg) expected = (-76.8933333297525, -76.8579444453814, 39.3381111142888, 39.4326111107889) self.assertEqual(expected, bbox.getBoundingBox())
def test_intersection(): ridx = RTreeIndex() ridx.insert(Point(10, 10)) ridx.insert(Point(100, 100)) q = ridx.intersection(BoundingBox(0, 0, 100, 100)) print(q) q = ridx.intersection(BoundingBox(0, 0, 90, 90)) print(q)
def __init__(self, depth=0, bbox=None): self.ne = self.se = self.sw = self.nw = None self.depth = depth if depth == 0: # Let's say our world can only be a third of the maximal float value max = 1500 #sys.float_info.max/3 self.bbox = BoundingBox(-max, -max, max, max) else: if not bbox: raise ValueError("No bounding box given") self.bbox = bbox self.items = []
def getBoundingBoxes(dets, isGT, bbFormat, coordType, allBoundingBoxes=None, allClasses=None, imgSize=(0, 0)): """Read txt files containing bounding boxes (ground truth and detections).""" if allBoundingBoxes is None: allBoundingBoxes = BoundingBoxes() if allClasses is None: allClasses = [] # Read GT detections from txt file # Each line of the files in the groundtruths folder represents a ground truth bounding box # (bounding boxes that a detector should detect) # Each value of each line is "class_id, x, y, width, height" respectively # Class_id represents the class of the bounding box # x, y represents the most top-left coordinates of the bounding box # x2, y2 represents the most bottom-right coordinates of the bounding box #nameOfImage = dets[0] for i in range(len(dets)): dets1 = dets[i] nameOfImage = dets1[0] if isGT: # idClass = int(splitLine[0]) #class idClass = 'person' # class x = dets1[1] y = dets1[2] w = dets1[3] h = dets1[4] bb = BoundingBox(nameOfImage, idClass, x, y, w, h, coordType, imgSize, BBType.GroundTruth, format=bbFormat) else: # idClass = int(splitLine[0]) #class idClass = 'person' # class confidence = dets1[2] x = dets1[3] y = dets1[4] w = dets1[5] h = dets1[6] bb = BoundingBox(nameOfImage, idClass, x, y, w, h, coordType, imgSize, BBType.Detected, confidence, format=bbFormat) allBoundingBoxes.addBoundingBox(bb) if idClass not in allClasses: allClasses.append(idClass) return allBoundingBoxes, allClasses
def getBoundingBoxes(directory, isGT, bbFormat, allBoundingBoxes=None, allClasses=None): """Read txt files containing bounding boxes (ground truth and detections).""" if allBoundingBoxes == None: allBoundingBoxes = BoundingBoxes() if allClasses == None: allClasses = [] # Read ground truths os.chdir(directory) files = glob.glob("*.txt") files.sort() # Read GT detections from txt file # Each line of the files in the groundtruths folder represents a ground truth bounding box (bounding boxes that a detector should detect) # Each value of each line is "class_id, x, y, width, height" respectively # Class_id represents the class of the bounding box # x, y represents the most top-left coordinates of the bounding box # x2, y2 represents the most bottom-right coordinates of the bounding box for f in files: nameOfImage = f.replace(".txt", "") fh1 = open(f, "r") for line in fh1: line = line.replace("\n", "") if line.replace(' ', '') == '': continue splitLine = line.split(" ") if isGT: # idClass = int(splitLine[0]) #class idClass = (splitLine[0]) # class x = float(splitLine[1]) y = float(splitLine[2]) w = float(splitLine[3]) h = float(splitLine[4]) bb = BoundingBox(nameOfImage, idClass, x, y, w, h, CoordinatesType.Absolute, (0, 0), BBType.GroundTruth, format=bbFormat) else: # idClass = int(splitLine[0]) #class idClass = (splitLine[0]) # class confidence = float(splitLine[1]) x = float(splitLine[2]) y = float(splitLine[3]) w = float(splitLine[4]) h = float(splitLine[5]) bb = BoundingBox(nameOfImage, idClass, x, y, w, h, CoordinatesType.Absolute, (0, 0), BBType.Detected, confidence, format=bbFormat) allBoundingBoxes.addBoundingBox(bb) if idClass not in allClasses: allClasses.append(idClass) fh1.close() return allBoundingBoxes, allClasses
class mAPTest(unittest.TestCase): def setUp(self): self.bb1 = BoundingBox(0, 50, 50, 100) self.bb2 = BoundingBox(30, 50, 40, 80) def test1(self): a = np.full_like(np.arange(10, dtype=np.double), 0.1) self.assertEqual({0.5: 0.1, 1: 0.1}, getThreshold(a, 10, 2)) def test2(self): a = np.asarray(range(1,101)) / 100.0 b = np.asarray((range(1,11))) / 10.0 c = {(v+1)/10.0: k for v, k in enumerate(b)} self.assertEquals(c, getThreshold(a, 100, 10)) def testOverlapX(self): bb1 = BoundingBox(0, 50, 0, 50) bb2 = BoundingBox(30, 80, 0, 50) self.assertEquals(20, bb1.x_overlap(bb2)) def testOverlapY1(self): bb1 = BoundingBox(0, 50, 0, 50) bb2 = BoundingBox(30, 80, 0, 50) self.assertEquals(50, bb1.y_overlap(bb2)) def testOverlapY2(self): bb1 = BoundingBox(0, 50, 50, 100) bb2 = BoundingBox(30, 80, 90, 105) self.assertEquals(10, bb1.y_overlap(bb2)) def testOverlapX2(self): self.assertEqual(20, self.bb1.x_overlap(self.bb2)) def testOverlapY3(self): self.assertEqual(30, self.bb1.y_overlap(self.bb2)) def testIntersection(self): self.assertEqual(600, self.bb1.intersection(self.bb2)) def testUnion(self): self.assertEqual(2700, self.bb1.union(self.bb2)) def testIOU(self): self.assertEqual((600./2700), self.bb1.iou(self.bb2))
def computeCropPadImageLocation(bbox_tight, image): """TODO: Docstring for computeCropPadImageLocation. :returns: TODO """ # Center of the bounding box bbox_center_x = bbox_tight.get_center_x() bbox_center_y = bbox_tight.get_center_y() image_height = image.shape[0] image_width = image.shape[1] # Padded output width and height output_width = bbox_tight.compute_output_width() output_height = bbox_tight.compute_output_height() roi_left = max(0.0, bbox_center_x - (output_width / 2.)) roi_bottom = max(0.0, bbox_center_y - (output_height / 2.)) # Padded roi width left_half = min(output_width / 2., bbox_center_x) right_half = min(output_width / 2., image_width - bbox_center_x) roi_width = max(1.0, left_half + right_half) # Padded roi height top_half = min(output_height / 2., bbox_center_y) bottom_half = min(output_height / 2., image_height - bbox_center_y) roi_height = max(1.0, top_half + bottom_half) # Padded image location in the original image objPadImageLocation = BoundingBox(roi_left, roi_bottom, roi_left + roi_width, roi_bottom + roi_height) return objPadImageLocation
def filter(self, frame): current = self.preprocess_frame(frame) if self.background_age > self.max_background_age: self.background_frame = current self.background_age = 0 if self.last_frame is not None: current = self.last_frame # prevent comparing background with itself self.last_frame = current self.background_age += 1 frame_delta = cv2.absdiff(self.background_frame, current) # show(frame_delta, 'delta') result = cv2.threshold(frame_delta, 30, 255, cv2.THRESH_BINARY)[1] # show(result, 'threshold') result = cv2.dilate(result, None, iterations=20) # show(result, 'dilated') img, contours, _ = cv2.findContours(result.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = [] for contour in contours: if self.min_contour_area < cv2.contourArea( contour) < self.max_contour_area: bounding_boxes.append(BoundingBox(*cv2.boundingRect(contour))) # cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2) return result, bounding_boxes
def subdivide(self): #print "self:",self.bbox l = self.bbox.ul.x r = self.bbox.lr.x t = self.bbox.ul.y b = self.bbox.lr.y mX = (l + r) / 2 mY = (t + b) / 2 self.northEast = QuadTree(BoundingBox(Point(mX, t), Point(r, mY)), self.maxPoints) self.southEast = QuadTree(BoundingBox(Point(mX, mY), Point(r, b)), self.maxPoints) self.southWest = QuadTree(BoundingBox(Point(l, mY), Point(mX, b)), self.maxPoints) self.northWest = QuadTree(BoundingBox(Point(l, t), Point(mX, mY)), self.maxPoints)
def createEvalBBoxes(imgNames): gtBBoxes = BoundingBoxes() for name in imgNames: path = os.path.join('../VOCdevkit/VOC2007/Annotations', name + '.xml') root = ET.parse(path).getroot() height = _findNode(root.find('size'), 'height', parse=int) width = _findNode(root.find('size'), 'width', parse=int) for element in root.iter('object'): box, class_name, difficult = parseBBoxes(element) if difficult: continue gtBBoxes.addBoundingBox( BoundingBox(imageName=name, classId=class_name, x=box[0, 0], y=box[0, 1], w=box[0, 2], h=box[0, 3], typeCoordinates=CoordinatesType.Absolute, bbType=BBType.GroundTruth, format=BBFormat.XYX2Y2, imgSize=(width, height))) with open('validationBBoxes.pickle', 'wb') as f: pickle.dump(gtBBoxes, f) print('create EvalBBoxes over!')
def testInit(self): print 'testInit ...' validFile = 'tests/WLBG-geog.csv' presPts = PresencePoints(validFile, 'WLBG') bbox = BoundingBox(presPts.points, presPts.epsg)
def get_bounding_boxes_from_xml(xml_path, coordType, bbFormat, allBoundingBoxes, allClasses, isGT): assert coordType == CoordinatesType.Absolute assert bbFormat == BBFormat.XYX2Y2 assert isGT is True tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) nameOfImage = xml_path.replace(".xml", "") for obj in root.iter('object'): difficult = obj.find('difficult').text cls_name = obj.find('name').text if int(difficult) == 1: continue xmlbox = obj.find('bndbox') b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text)) bb = BoundingBox(nameOfImage, cls_name, float(xmlbox.find('xmin').text), float(xmlbox.find('ymin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymax').text), coordType, (w, h), BBType.GroundTruth, format=bbFormat) allBoundingBoxes.addBoundingBox(bb) if cls_name not in allClasses: allClasses.append(cls_name)
def setup(self): self.q = QuadTree( BoundingBox(Point(0, 0), Point(self.width, self.height)), 3) self.points = [] self.colors = [ "#000", "#F00", "#0f0", "#00f", "#ff0", "#0ff", "#0f0" ]
def get_videos(self): """Docstring for get_videos. :returns: returns video frames in each sub folder of vot directory """ vot_folder = self.vot_folder sub_vot_dirs = self.find_subfolders(vot_folder) for vot_sub_dir in sub_vot_dirs: list_of_frames = sorted( glob.glob(os.path.join(vot_folder, vot_sub_dir, '*.jpg'))) if not list_of_frames: logger.error('vot folders should contain only .jpg images') self.videos[vot_sub_dir] = list_of_frames bbox_gt_file = os.path.join(vot_folder, vot_sub_dir, 'groundtruth.txt') bbox_gt_coords = [] with open(bbox_gt_file, 'r') as f: for line in f: co_ords = line.strip().split(',') co_ords = [int(float(co_ord)) for co_ord in co_ords] ax, ay, bx, by, cx, cy, dx, dy = co_ords x1 = min(ax, min(bx, min(cx, dx))) - 1 y1 = min(ay, min(by, min(cy, dy))) - 1 x2 = max(ax, max(bx, max(cx, dx))) - 1 y2 = max(ay, max(by, max(cy, dy))) - 1 bbox = BoundingBox(x1, y1, x2, y2) bbox_gt_coords.append(bbox) self.annotations[vot_sub_dir] = bbox_gt_coords return self.videos, self.annotations
def view(self, window): fov = 0.14 # Hack: Roughly the factor needed to account for the FOV w = window.width * fov h = window.height * fov bbox = BoundingBox(self.location.x - w / 2, self.location.y - h / 2, self.location.x + w / 2, self.location.y + h / 2) # bbox.draw() return bbox
def __init__(self, presFile, startDate, endDate, species, inDir = '.', outDir = '.', numProcs = 10, numTrials = 10, logger = None): # Create the MmxConfig object. mmxConfig = MmxConfig() mmxConfig.initializeFromValues(presFile, startDate, endDate, species, inDir, outDir, numProcs, numTrials) # Define the bounding box. presPts = PresencePoints(presFile, species) bbox = BoundingBox(presPts.points, presPts.epsg) mmxConfig.setUlx(bbox.getUlx()) mmxConfig.setUly(bbox.getUly()) mmxConfig.setLrx(bbox.getLrx()) mmxConfig.setLry(bbox.getLry()) mmxConfig.setEPSG(bbox.getEpsg()) super(ConfigureMmxRun, self).__init__(mmxConfig, 'ConfigureMmxRun', logger) # Log what we have so far. self.logHeader() self.logger.info(str(self.config))
def computeBBfromBB(bb_coords): """ Method that converts np array of coords (xmin,xmax,ymin,ymax) into a bounding box. Args: bb_coords: the bounding box coordinates """ return BoundingBox(bb_coords[0], bb_coords[1], bb_coords[2], bb_coords[3])
def boxRatioIsEqual(box: BoundingBox, aspectRatio: float): """Checks if a boxes aspect ratio is equal to the given aspect ratio. Args: box (BoundingBox): box to check aspect ratio for aspectRatio (Tuple[int,int]): aspect ratio to check box against (width, height) """ diff = abs(box.aspectRatio() - aspectRatio) return diff < epsilon
def updateLocation(self, newLocation): self.previousLocation = self.location self.location = newLocation self.bbox = BoundingBox(self.location.x - self.width / 2, self.location.y - self.height / 2, self.location.x + self.width / 2, self.location.y + self.height / 2) self.polygon = Polygon.createBoundingBoxPolygon( Vector(self.location.x - self.width / 2, self.location.y - self.height / 2), Vector(self.location.x + self.width / 2, self.location.y + self.height / 2)) self.polygon.convex = True
def locate_templates(img, templates, start, stop, threshold): #print('Locating Templates...') #print(f'templates in locate: {templates}') locations, scale = match(img, templates, start, stop, threshold) img_locations = [] for i in range(len(templates)): w, h = templates[i].shape[::-1] w *= scale h *= scale img_locations.append([ BoundingBox(pt[0], pt[1], w, h) for pt in zip(*locations[i][::-1]) ]) return img_locations
def iou(self, x1, y1, x2, y2): bounding_box1 = BoundingBox(x1 * self.stride - self.half_box_length, x1 * self.stride + self.half_box_length, y1 * self.stride - self.half_box_length, y1 * self.stride + self.half_box_length) bounding_box2 = BoundingBox(x2 * self.stride - self.half_box_length, x2 * self.stride + self.half_box_length, y2 * self.stride - self.half_box_length, y2 * self.stride + self.half_box_length) assert (bounding_box1.iou(bounding_box2) == bounding_box2.iou( bounding_box1)) return bounding_box1.iou(bounding_box2)
def __init__(self, depth=0, bbox=None): self.ne = self.se = self.sw = self.nw = None self.depth = depth if depth == 0: # Let's say our world can only be a third of the maximal float value max = 1500#sys.float_info.max/3 self.bbox = BoundingBox(-max, -max, max, max) else: if not bbox: raise ValueError("No bounding box given") self.bbox = bbox self.items = []
class PointSet(MyJSONEncoder.AbstractJSONEncoder): def __init__(self): self.points = [] self.boundingBox = BoundingBox() def addPoint(self, p): if not isinstance(p, Point): Logger().error( "PointSet: il punto passato alla bounding box non e' un punto valido" ) return False self.points.append(p) if not self.boundingBox.updateBoundingBox(p): Logger().error("PointSet: errore nell'update della bounding box") return False return True def getMin(self): return self.boundingBox.getMin() def getMax(self): return self.boundingBox.getMax() def size(self): return len(self.points) def decodeJson(self): ret = { "Points": self.size(), "BoundingBox": { "Min": self.getMin(), "Max": self.getMax(), }, "Coordinate": self.points } return ret
def create_particles(num_particles: int, max_x: int, max_y: int, min_x: int, min_y: int, width: int, height: int, image) -> List[Particles]: particles = [] for i in range(0, num_particles): x = random.randint(min_y, max_x) y = random.randint(min_y, max_y) bbox = BoundingBox(x, y, width, height, 640, 480, 0, 0) histogram = generate_histograms(bbox, image) particle = Particles(bbox, histogram, 1) particles.append(particle) return particles
def computeBBfromSM(segMask): """ Method that computes bounding box from a binary segmentation mask with only a single object present Arguments: segMask: 2D binary image """ if len(segMask.shape) != 2: raise ValueError('Cannot compute bounding box from non-2D image') x_coords, y_coords = np.where(segMask > 0) return BoundingBox(np.min(y_coords), np.max(y_coords), np.min(x_coords), np.max(x_coords))
def get_image_bounding_box(self) -> BoundingBox: mx, my = self._lat_lon_to_meters(self.center_lat, self.center_lng) pixel_x, pixel_y = self._meters_to_pixels(mx, my) w_pixel_x, e_pixel_x = pixel_x - self.image_w / 2, pixel_x + self.image_w / 2 s_pixel_y, n_pixel_y = pixel_y - self.image_h / 2, pixel_y + self.image_h / 2 w_meter_x = self._pixels_to_meters(w_pixel_x, self.zoom) e_meter_x = self._pixels_to_meters(e_pixel_x, self.zoom) s_meter_y = self._pixels_to_meters(s_pixel_y, self.zoom) n_meter_y = self._pixels_to_meters(n_pixel_y, self.zoom) s_lat, w_lng = self._meters_to_lat_lon(w_meter_x, s_meter_y) n_lat, e_lng = self._meters_to_lat_lon(e_meter_x, n_meter_y) return BoundingBox(s_lat, w_lng, n_lat, e_lng)
class QuadNode: def __init__(self, depth=0, bbox=None): self.ne = self.se = self.sw = self.nw = None self.depth = depth if depth == 0: # Let's say our world can only be a third of the maximal float value max = 1500#sys.float_info.max/3 self.bbox = BoundingBox(-max, -max, max, max) else: if not bbox: raise ValueError("No bounding box given") self.bbox = bbox self.items = [] # # Add an item to the quad tree # item needs to have a member bbox containing it's bounding box # def add(self, item): if self.depth < 8 and self.ne == None: cx = self.bbox.center.x cy = self.bbox.center.y self.ne = QuadNode(self.depth + 1, BoundingBox(cx, cy, self.bbox.maxX, self.bbox.maxY)) self.se = QuadNode(self.depth + 1, BoundingBox(cx, self.bbox.minY, self.bbox.maxX, cy)) self.sw = QuadNode(self.depth + 1, BoundingBox(self.bbox.minX, self.bbox.minY, cx, cy)) self.nw = QuadNode(self.depth + 1, BoundingBox(self.bbox.minX, cy, cx, self.bbox.maxY)) if self.depth == 8: self.items.append(item) return if self.ne.bbox.overlaps(item.bbox): self.ne.add(item) if self.se.bbox.overlaps(item.bbox): self.se.add(item) if self.sw.bbox.overlaps(item.bbox): self.sw.add(item) if self.nw.bbox.overlaps(item.bbox): self.nw.add(item) # # Remove an item from the quad tree # This only works if the item still has the same bounding box as it had at the time of adding # def remove(self, item): if self.depth == 8: self.items.remove(item) return if self.ne.bbox.overlaps(item.bbox): self.ne.remove(item) if self.se.bbox.overlaps(item.bbox): self.se.remove(item) if self.sw.bbox.overlaps(item.bbox): self.sw.remove(item) if self.nw.bbox.overlaps(item.bbox): self.nw.remove(item) def drawCollision(self, item): self.bbox.draw() if self.depth == 8: return if self.ne.bbox.overlaps(item.bbox): self.ne.drawCollision(item) if self.se.bbox.overlaps(item.bbox): self.se.drawCollision(item) if self.sw.bbox.overlaps(item.bbox): self.sw.drawCollision(item) if self.nw.bbox.overlaps(item.bbox): self.nw.drawCollision(item) def collision(self, object): offsets = [] objects = [] self.__collision__(object, offsets, objects) if len(offsets) == 0: return False, None max = -1 result = None for offset in offsets: if offset.length() > max: result = offset max = offset.length() test = result if Vector(0, 1).dot(result) < 0: test = test.multiply(-1) p = Vector( (object.bbox.minX+object.bbox.maxX)/2, object.bbox.minY) t = p.add(test) glPointSize(10) glBegin(GL_POINTS) glVertex2f(p.x, p.y) glEnd() glPointSize(1) glLineWidth(10) glBegin(GL_LINES) glVertex2f(p.x, p.y) glVertex2f(t.x, t.y) glEnd() glLineWidth(1) return True, result def __collision__(self, object, offsets, objects): if self.depth == 8: for item in self.items: if objects.count(item) == 0: collides, offset = item.collision(object) if collides: offsets.append(offset) objects.append(item) if self.ne and self.ne.bbox.overlaps(object.bbox): self.ne.__collision__(object, offsets, objects) if self.se and self.se.bbox.overlaps(object.bbox): self.se.__collision__(object, offsets, objects) if self.sw and self.sw.bbox.overlaps(object.bbox): self.sw.__collision__(object, offsets, objects) if self.nw and self.nw.bbox.overlaps(object.bbox): self.nw.__collision__(object, offsets, objects) def draw(self, view): drawn = [] self.__draw__(view, drawn) # print len(drawn) def __draw__(self, view, drawn): if self.depth == 8: for item in self.items: if drawn.count(item) == 0: item.draw() drawn.append(item) if self.ne and self.ne.bbox.overlaps(view.bbox): self.ne.__draw__(view, drawn) if self.se and self.se.bbox.overlaps(view.bbox): self.se.__draw__(view, drawn) if self.sw and self.sw.bbox.overlaps(view.bbox): self.sw.__draw__(view, drawn) if self.nw and self.nw.bbox.overlaps(view.bbox): self.nw.__draw__(view, drawn)
def updateBoundingBox(self): size = len(self.vertices) self.bbox = BoundingBox(self.vertices[0].x,self.vertices[0].y,self.vertices[0].x,self.vertices[0].y) for vertex in range(1, size): self.bbox.add(self.vertices[vertex].x, self.vertices[vertex].y)
class Polygon: def __init__(self): self.vertices = [] self.monotones = None self.triangles = None self.bbox = None # If this is False, this does not mean it is no convex, but if it is True, it is! self.convex = False def addVertex(self, p): self.vertices.append(p) self.updateBoundingBox() if len(self.vertices) == 3: self.convex = True else: self.convex = False # # Draw this polygon # def draw(self): self.__draw__(0) def __draw__(self, level): if len(self.vertices) < 3: raise ValueError("This ain't no polygon! A duogon, at best!") if len(self.vertices) == 3: glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); glColor3f (.1, .1, .1) glBegin(GL_TRIANGLES) glVertex2f(self.vertices[0].x, self.vertices[0].y) glVertex2f(self.vertices[1].x, self.vertices[1].y) glVertex2f(self.vertices[2].x, self.vertices[2].y) glEnd() if self.monotones and self.triangles: raise ValueError("A polygon containing both monotones and triangles? Something went wrong") if self.monotones: for m in self.monotones: m.__draw__(level + 1) if self.triangles: for t in self.triangles: t.__draw__(level + 1) if level == 0: glColor3f(.9, .9, .9) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glEnable(GL_POLYGON_OFFSET_LINE); glPolygonOffset(-1.,-1.); self.__drawPolygon__() def __drawPolygon__(self): glBegin(GL_POLYGON) size = len(self.vertices) for vertex in range(size): glVertex2f(self.vertices[vertex].x, self.vertices[vertex].y) glVertex2f(self.vertices[(vertex+1)%size].x, self.vertices[(vertex+1)%size].y) glEnd() # Line segment 1: p + t*r # Line segment 2: q + u*s @staticmethod def __LineSegmentIntersection__(p, r, q, s): rCrossS = r.cross(s) if rCrossS == 0: return -1, -1 qMinusP = q.subtract(p) rhsT = qMinusP.cross(s) t = rhsT/rCrossS rhsR = qMinusP.cross(r) u = rhsR/rCrossS return t, u # # Check for collisions # \return True|False, CollisionVector # def collisionActor(self, actor): if not actor.bbox.overlaps(self.bbox): return False, Vector(0,0) # Line segment 1: p + t*r # Line segment 2: q + u*s p = Vector(actor.location.x, actor.location.y-actor.height/2) r = Vector(0, actor.height-1) size = len(self.vertices) for vertex in range(size): # t = (q - p) x s /(r x s) # u = (q - p) x r /(r x s) q = self.vertices[vertex] qPlusS = self.vertices[(vertex+1)%size] s = qPlusS.subtract(q) t, u = Polygon.__LineSegmentIntersection__(p, r, q, s) if t >= 0 and t < 1 and u >= 0 and u < 1: # return True, p.add(r.multiply(t)).add(r.multiply(0.5)) return True, r.multiply(t)#.add(Vector(0, -.001)) return False, Vector(0,0) # # Check if this polygon collides with another polygon # \return True|False, CollisionVector # def collision(self, other): collides, axis, dist = self.__privateCollision__(other) if collides: return True, axis.multiply(dist) return False, None def __privateCollision__(self, other): if not self.bbox.overlaps(other.bbox): return False, None, None if self.convex: minAxis = None minDist = sys.float_info.max collides, minAxis, minDist = self.__privateCollisionConvex__(other, minAxis, minDist) if not collides: return False, minAxis, minDist return other.__privateCollisionConvex__(self, minAxis, minDist) if self.monotones: collides = False newCollides = False newAxis = None newDist = None axis = None dist = -1 for m in self.monotones: newCollides, newAxis, newDist = m.__privateCollision__(other) if newCollides: collides = True if newDist > dist: dist = newDist axis = newAxis return collides, axis, dist if self.triangles: collides = False newCollides = False newAxis = None newDist = None axis = None dist = -1 for t in self.triangles: newCollides, newAxis, newDist = t.__privateCollision__(other) if newCollides: collides = True if newDist > dist: dist = newDist axis = newAxis return collides, axis, dist # Internal collision method def __privateCollisionConvex__(self, other, minAxis, minDist): size = len(self.vertices) for i in range(size): v1 = self.vertices[i] v2 = self.vertices[(i+1)%size] perp = Polygon.__perpendicularVector__(self, v1, v2) minSelf = maxSelf = minOther = maxOther = None minSelf, maxSelf = self.__projectOnAxis__(perp) minOther, maxOther = other.__projectOnAxis__(perp) dist = Polygon.__getIntervalDistance__(minSelf, maxSelf, minOther, maxOther) if dist > 0.: return False, minAxis, minDist elif abs(dist) < minDist: minDist = abs(dist) minAxis = perp return True, minAxis, minDist # # Project all vertices of this polygon onto an axis # Get the min and max values # def __projectOnAxis__(self, axis): min = max = axis.dot(self.vertices[0]) for i in range(1, len(self.vertices)): product = axis.dot(self.vertices[i]) if product < min: min = product if product > max: max = product return min, max # # Get the distance beween two 1-dimensional intervals # @staticmethod def __getIntervalDistance__(min1, max1, min2, max2): if min1 < min2: return min2 - max1 else: return min1 - max2 # # Get the perpendicular vector on a line segment defined by 2 poins # @staticmethod def __perpendicularVector__(self, v1, v2): dy = (v1.y - v2.y) dx = (v1.x - v2.x) d = sqrt(dy*dy + dx*dx) if d == 0: return Vector(1, 0) return Vector(-dy / d, dx / d) def __selfIntersects__(self): size = len(self.vertices) for vertex1 in range(size): p = self.vertices[vertex1] pPlusR = self.vertices[(vertex1+1)%size] r = pPlusR.subtract(p) for vertex2 in range(vertex1, size): if vertex1 == vertex2: continue q = self.vertices[vertex2] qPlusS = self.vertices[(vertex2+1)%size] s = qPlusS.subtract(q) t, u = Polygon.__LineSegmentIntersection__(p, r, q, s) if t >= 0 and t < 1 and u >= 0 and u < 1: return True return False # TODO def cut(self, other): sizeSelf = len(self.vertices) sizeOther = len(other.vertices) # Line segment 1: p + t*r # Line segment 2: q + u*s for vertexSelf in range(sizeSelf): p = self.vertices[vertexSelf] pPlusR = self.vertices[(vertexSelf+1)%sizeSelf] r = pPlusR.subtract(p) for vertexOther in range(sizeOther): # t = (q - p) x s /(r x s) # u = (q - p) x r /(r x s) q = other.vertices[vertexOther] qPlusS = other.vertices[(vertexOther+1)%sizeOther] s = qPlusS.subtract(q) rCrossS = r.cross(s) qMinusP = q.subtract(p) rhsT = qMinusP.cross(s) t = rhsT/rCrossS rhsR = qMinusP.cross(r) u = rhsR/rCrossS if t >= 0 and t < 1 and u >= 0 and u < 1: intersect = p.add(r.multiply(t)) glColor3f(0.8, 0.8, 0.2) glPointSize(6) intersect.draw() # # Check if the given point is inside the polygon # def inside(self, p): size = len(self.vertices) count = 0 for i in range(size): v1 = self.vertices[i] v2 = self.vertices[(i+1)%size] if (v1.y > p.y and v2.y <= p.y) or \ (v1.y <= p.y and v2.y > p.y): div = (v1.x - v2.x) if div == 0: if p.x - v1.x > 0: count += 1 m = (v1.y - v2.y) / div if p.x - (v1.x + (p.y - v1.y)/m) > 0: count += 1 return count%2 == 1 @staticmethod def __interiorAngleGreaterThanPi__(v1, v2, v3): cross = v1.subtract(v2).cross(v3.subtract(v2)) if cross < 0: return True return False # Doubly Connected edge list: a data structure which can be used in monotone decomposition and triangulation of a polygon class DCELHalfEdge: def __init__(self, origin): self.origin = origin self.twin = None self.next = None self.prev = None self.face = None self.helper = None def start(self): return self.origin.v def end(self): return self.next.origin.v def xDistTo(self, v): # y - y1 = m(x - x1) # x = x1 + (y - y1)/m div = (self.start().x - self.end().x) if div == 0: return v.x - self.start().x m = (self.start().y - self.end().y) / div return v.x - (self.start().x + (v.y - self.start().y)/m) def mark(self): glLineWidth(10) glBegin(GL_LINES) glVertex2f(self.start().x, self.start().y) glVertex2f(self.end().x, self.end().y) glEnd() glLineWidth(1) class DCELVertex: def __init__(self, v): self.v = v self.leavingEdge = None class DCELFace: def __init__(self, edge): self.edge = edge self.visited = False class DCELNextEdgeIterator: def __init__(self, edge): self.start = edge self.current = edge def next(self): self.current = self.current.next if self.current == self.start: return None return self.current class DCELLeavingEdgeIterator: def __init__(self, edge): self.start = edge self.current = edge def next(self): self.current = self.current.prev.twin if self.current == self.start: return None return self.current # Search tree, implemented initially as a flat array (Laziness) class Tree: def __init__(self): self.edges = [] # self.count = 0 def add(self, edge): self.edges.append(edge) def remove(self, edge): self.edges.remove(edge) def edgeLeftOf(self, v): d = sys.float_info.max e = None for edge in self.edges: if (edge.start().y >= v.y and edge.end().y < v.y) or \ (edge.start().y < v.y and edge.end().y >= v.y): distToEdge = edge.xDistTo(v) if distToEdge > 0 and d > distToEdge: d = distToEdge e = edge return e def draw(self): print "hmpf" for edge in self.edges: glLineWidth(6) glBegin(GL_LINES) glVertex2f(edge.start().x, edge.start().y) glVertex2f(edge.end().x, edge.end().y) glEnd() glLineWidth(1) # Is it possible to add a diagonal between two vertices def __canAddDiagonal__(self, vi1, vi2): size = len(self.vertices) p = self.vertices[vi1] pPlusR = self.vertices[vi2] r = pPlusR.subtract(p) for v in range(size): # if (vi1==v and vi2==(v+1)%size) or (vi2==v and vi1==(v+1)%size): # return False q = self.vertices[v] qPlusS = self.vertices[(v+1)%size] s = qPlusS.subtract(q) t, u = Polygon.__LineSegmentIntersection__(p, r, q, s) if t > 0 and t < 1 and u > 0 and u < 1: return False midDiagonal = Vector((p.x+pPlusR.x)/2, (p.y+pPlusR.y)/2) return self.inside(midDiagonal) # Add a diagonal to the DCEL of this polygon def __addDiagonal__(self, vi1, vi2, dcelVertices): v1 = dcelVertices[vi1] v2 = dcelVertices[vi2] # Find the common face for v1 and v2 face = None e1 = v1.leavingEdge it1 = Polygon.DCELLeavingEdgeIterator(e1) while e1: face1 = e1.face e2 = v2.leavingEdge it2 = Polygon.DCELLeavingEdgeIterator(e2) while e2: if face1 == e2.face and face1 != None: face = face1 break e2 = it2.next() e1 = it1.next() # Find the next and previous edges for both diagonals prevEdge1 = None nextEdge1 = None prevEdge2 = None nextEdge2 = None e = face.edge it = Polygon.DCELNextEdgeIterator(e) while e: if e.origin == v1: prevEdge1 = e.prev nextEdge1 = e if e.origin == v2: prevEdge2 = e.prev nextEdge2 = e e = it.next() # Connect 2 new half edges diagonal1 = Polygon.DCELHalfEdge(v1) diagonal1.prev = prevEdge1 prevEdge1.next = diagonal1 diagonal1.next = nextEdge2 nextEdge2.prev = diagonal1 diagonal2 = Polygon.DCELHalfEdge(v2) diagonal2.prev = prevEdge2 prevEdge2.next = diagonal2 diagonal2.next = nextEdge1 nextEdge1.prev = diagonal2 diagonal1.twin = diagonal2 diagonal2.twin = diagonal1 # Connect new faces face1 = Polygon.DCELFace(diagonal1) face2 = Polygon.DCELFace(diagonal2) e1 = diagonal1 it1 = Polygon.DCELNextEdgeIterator(e1) while e1: e1.face = face1 e1 = it1.next() e2 = diagonal2 it2 = Polygon.DCELNextEdgeIterator(e2) while e2: e2.face = face2 e2 = it2.next() # Construct the Doubly connected edge list # \param dcelVertices A list of DCELVertex objects, must be empty def __constructDCEL__(self, dcelVertices): size = len(self.vertices) currentVertex = None # Create all DCEL Vertices with a leaving edge for i in range(size): # Create the current vertex or use the previously stored last one, if this is the last vertex v = self.vertices[i] currentVertex = Polygon.DCELVertex(v) # Create the DCEL Half Edge currentEdge = Polygon.DCELHalfEdge(currentVertex) # Connect currentVertex.leavingEdge = currentEdge dcelVertices.append(currentVertex) # Create all twin half edges for i in range(size): currentVertex = dcelVertices[i] nextVertex = dcelVertices[(i+1)%size] currentEdge = currentVertex.leavingEdge twinEdge = Polygon.DCELHalfEdge(nextVertex) currentEdge.twin = twinEdge twinEdge.twin = currentEdge # Connect all edges for i in range(size): prevVertex = dcelVertices[i-1] currentVertex = dcelVertices[i] nextVertex = dcelVertices[(i+1)%size] currentEdge = currentVertex.leavingEdge currentEdge.prev = prevVertex.leavingEdge currentEdge.next = nextVertex.leavingEdge twinEdge = currentEdge.twin twinEdge.prev = nextVertex.leavingEdge.twin twinEdge.next = prevVertex.leavingEdge.twin # Connect the single face startVertex = dcelVertices[0] startEdge = startVertex.leavingEdge face = Polygon.DCELFace(startEdge) e = startEdge it = Polygon.DCELNextEdgeIterator(e) while e: e.face = face e = it.next() # Sort the vertices of this polygon according to y coordinate def __ySortVertices__(self, sortedVertices): size = len(self.vertices) for vertex in range(size): v = self.vertices[vertex] sizeSorted = len(sortedVertices) found = False for index in range(sizeSorted): if v.above(self.vertices[sortedVertices[index]]): sortedVertices.insert(index, vertex) found = True break if not found: sortedVertices.append(vertex) @staticmethod def __constructPolygonsFromDCEL__(dcelVertices, polygons): # Iterator all leaving edges on all vertices to find all faces for vertex in dcelVertices: e1 = vertex.leavingEdge it1 = Polygon.DCELLeavingEdgeIterator(e1) polygon = Polygon() while e1: if e1.face and not e1.face.visited: e1.face.visited = True polygon = Polygon() e2 = e1.face.edge it2 = Polygon.DCELNextEdgeIterator(e2) while e2: polygon.addVertex(e2.origin.v) e2 = it2.next() polygons.append(polygon) e1 = it1.next() def triangulate(self): size = len(self.vertices) if size <= 3: return # Monotone decomposition # Computation Geometry Algorithms and Applications, page 50 startVertex = 1 endVertex = 2 regularVertex = 3 mergeVertex = 4 splitVertex = 5 vertexType = [] # Determine the type for each vertex for vertex in range(size): v = self.vertices[vertex] vPrev = self.vertices[vertex-1] vNext = self.vertices[(vertex+1)%size] if v.above(vPrev) and v.above(vNext): if Polygon.__interiorAngleGreaterThanPi__(vNext, v, vPrev): vertexType.append(splitVertex) else: vertexType.append(startVertex) elif vPrev.above(v) and vNext.above(v): if Polygon.__interiorAngleGreaterThanPi__(vNext, v, vPrev): vertexType.append(mergeVertex) else: vertexType.append(endVertex) else: vertexType.append(regularVertex) # glColor3f(1., 1., 1.) # glPolygonMode(GL_FRONT_AND_BACK,GL_LINE) # if False: # self.__privateDraw__() # for vertex in range(size): # v = self.vertices[vertex] # if vertexType[vertex] == startVertex: # glPointSize(12) # elif vertexType[vertex] == endVertex: # glPointSize(3) # elif vertexType[vertex] == splitVertex: # glPointSize(6) # elif vertexType[vertex] == mergeVertex: # glPointSize(9) # else: # glPointSize(1) # glColor3f(1., 2., 2.) # glBegin(GL_POINTS) # glVertex2f(v.x, v.y) # glEnd() # Construct doubly-connected edge list dcelVertices = [] self.__constructDCEL__(dcelVertices) # # TEST CODE : Check DCEL # # polys = [] # Polygon.__constructPolygonsFromDCEL__(dcelVertices, polys) # c = int(time.clock()*2)%(len(polys)+1) # if c == len(polys): # glColor3f(1., 1., .2) # self.__privateDraw__() # return # glColor3f(.2, .2, 1.) # polys[c].__privateDraw__() # # TEST CODE : Check prev, next, twin edges # # c = int(time.clock()*2)%(len(dcelVertices)) # v = dcelVertices[c] # glPointSize(30) # glBegin(GL_POINTS) # glVertex2f(v.v.x, v.v.y) # glEnd() # glPointSize(1) # v.leavingEdge.twin.mark() # glColor3f(1., 1., .2) # v.leavingEdge.twin.next.mark() # return # # TEST CODE : Check edgeLeftOf # # tree = Polygon.Tree() # e1 = Polygon.DCELHalfEdge(Polygon.DCELVertex(Vector(-1, 1))) # e2 = Polygon.DCELHalfEdge(Polygon.DCELVertex(Vector(-1, -1))) # e1.next = e2 # e2.prev = e1 # tree.add(e1) # tree.edgeLeftOf(Vector(0, 0)).mark() # return # Sort vertices top to bottom # Insertion sort sortedVertices = [] self.__ySortVertices__(sortedVertices) # Initialize the partition with the edges of the (possibly) not-monotone polygon tree = Polygon.Tree() # Handle each vertex, from top to bottom the polygon # Handling each vertex depends on the type of the vertex for vertex in sortedVertices: v = self.vertices[vertex] if vertexType[vertex] == startVertex: ei = dcelVertices[vertex].leavingEdge ei.helper = vertex # Add e_i to searchtree tree.add(ei) elif vertexType[vertex] == endVertex or vertexType[vertex] == mergeVertex: # Perform actions which are the same of end or merge vertices eiMinus1 = dcelVertices[vertex-1].leavingEdge # Add a diagonal if helper(e_i+1) is a merge vertex if vertexType[eiMinus1.helper] == mergeVertex: self.__addDiagonal__(vertex, eiMinus1.helper, dcelVertices) # Delete e_i from searchtree tree.remove(eiMinus1) # Perform actions which are unique to a merge vertex if vertexType[vertex] == mergeVertex: # Find edge directly to the right of vertex ej = tree.edgeLeftOf(v) if vertexType[ej.helper] == mergeVertex: self.__addDiagonal__(vertex, ej.helper, dcelVertices) # Set vertex as new helper ej.helper = vertex elif vertexType[vertex] == splitVertex: # Find edge directly to the left of vertex ej = tree.edgeLeftOf(v) self.__addDiagonal__(vertex, ej.helper, dcelVertices) # Set vertex as new helper ej.helper = vertex ei = dcelVertices[vertex].leavingEdge # Add e_i to tree and set vertex as helper ei.helper = vertex tree.add(ei) elif vertexType[vertex] == regularVertex: #If the interior of the polygon lies right of this vertex if self.vertices[(vertex+1)%size].y <= v.y and \ self.vertices[vertex-1].y > v.y: eiMinus1 = dcelVertices[vertex-1].leavingEdge helper = eiMinus1.helper # Add a diagonal if helper(e_i-1) is a merge vertex if vertexType[helper] == mergeVertex: self.__addDiagonal__(vertex, helper, dcelVertices) # Delete e_i-1 from searchtree tree.remove(eiMinus1) # Add e_i to tree and set vertex as helper ei = dcelVertices[vertex].leavingEdge ei.helper = vertex tree.add(ei) else: # Find edge directly to the left of vertex ej = tree.edgeLeftOf(v) if not ej: glPointSize(5) v.draw() glPointSize(1) return if vertexType[ej.helper] == mergeVertex: self.__addDiagonal__(vertex, ej.helper, dcelVertices) # Set vertex as new helper ej.helper = vertex # Construct the monotones self.monotones = [] Polygon.__constructPolygonsFromDCEL__(dcelVertices, self.monotones) # print "Monotones: " + str(len(self.monotones)) # if self.debug == 2: # c = int(time.clock()*2)%(len(self.monotones)+1) # if c == len(self.monotones): # glColor3f(1., 1., .2) # self.__privateDraw__() # return # glColor3f(.2, .2, 1.) # self.monotones[c].__privateDraw__() # for polygon in self.monotones: # polygon.__privateDraw__() # return # for m in self.monotones: # glColor3f(1., 1., 1.) # m.__privateDraw__() for m in self.monotones: if len(m.vertices) == 3: # if self.debug == 1 or self.debug == 3: # m.draw(1., .6, .0) continue monoDcelVertices = [] m.__constructDCEL__(monoDcelVertices) monotoneSortedVertices = [] m.__ySortVertices__(monotoneSortedVertices) stack = [] stack.append(monotoneSortedVertices[0]) stack.append(monotoneSortedVertices[1]) for j in range(2, len(monotoneSortedVertices)-1): if m.__onSameEdge__(stack[-1], monotoneSortedVertices[j]): poppedLast = stack.pop() vStack = stack[-1] while m.__canAddDiagonal__(vStack, monotoneSortedVertices[j]): m.__addDiagonal__(vStack, monotoneSortedVertices[j], monoDcelVertices) poppedLast = stack.pop() if len(stack) == 0: break; vStack = stack[-1] stack.append(poppedLast) stack.append(monotoneSortedVertices[j]) else: while len(stack) > 0: vi = stack.pop() if len(stack) > 0: m.__addDiagonal__(vi, monotoneSortedVertices[j], monoDcelVertices) stack.append(monotoneSortedVertices[j-1]) stack.append(monotoneSortedVertices[j]) stack.pop() while len(stack) > 1: m.__addDiagonal__(stack[-1], monotoneSortedVertices[-1], monoDcelVertices) stack.pop() m.triangles = [] Polygon.__constructPolygonsFromDCEL__(monoDcelVertices, m.triangles) # if self.debug == 1: # m.triangles[int(time.clock()*2)%len(m.triangles)].draw(1., .2, .2) # if self.debug == 3: # for t in m.triangles: # t.draw(1., .2, .2) # check if two vertices are on the same edge, given their indices def __onSameEdge__(self, i1, i2): size = len(self.vertices) return abs(i1-i2) == 1 or abs(i1-i2) == size-1 # Apply midpoint displacement to this polygon # The midpoint of each line segment will be displaced randomly # perpendicular to the line segment, distance between -factor and +factor def midpointDisplacement(self, factor): seed = random.randint(0, 10000) # if factor == 8: # seed = 8977 # seed = 9539 # seed = 1909 # seed = 5021 # seed = 6131 # Triangulation issue # seed = 6198 # Triangulation issue # seed = 3403 # Triangulation issue print factor print seed random.seed(seed) size = len(self.vertices) oldVertices = list(self.vertices) vertices = [] for vertex in range(size): p1 = self.vertices[vertex] p2 = self.vertices[(vertex+1)%size] vertices.append(p1) p = p1.subtract(p2).perpendicularVector() r = factor - random.random()*factor*2 x = (p1.x + p2.x)/2 + r*p.x y = (p1.y + p2.y)/2 + r*p.y vertices.append(Vector(x, y)) self.vertices = vertices if self.__selfIntersects__(): print "uh oh" self.vertices = oldVertices self.midpointDisplacement(factor/2) self.updateBoundingBox() def updateBoundingBox(self): size = len(self.vertices) self.bbox = BoundingBox(self.vertices[0].x,self.vertices[0].y,self.vertices[0].x,self.vertices[0].y) for vertex in range(1, size): self.bbox.add(self.vertices[vertex].x, self.vertices[vertex].y) @staticmethod def createBoundingBoxPolygon(pMin, pMax): p = Polygon() p.addVertex(Vector(pMin.x, pMax.y)) p.addVertex(Vector(pMin.x, pMin.y)) p.addVertex(Vector(pMax.x, pMin.y)) p.addVertex(Vector(pMax.x, pMax.y)) return p