From 73f27300caa3f97529f58aa7613bc6ace4d630d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Tue, 14 Nov 2017 02:49:02 +0100 Subject: [PATCH] save all mesh coordinates as mm-ints to avoid floating point errors --- src/c3nav/mapdata/render/data.py | 71 +++++++++++----------- src/c3nav/mapdata/render/engines/base3d.py | 4 +- src/c3nav/mapdata/render/engines/stl.py | 8 +-- src/c3nav/mapdata/render/renderer.py | 8 +-- src/c3nav/mapdata/utils/mesh.py | 9 ++- 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/c3nav/mapdata/render/data.py b/src/c3nav/mapdata/render/data.py index 73a3c52b..f79c24a6 100644 --- a/src/c3nav/mapdata/render/data.py +++ b/src/c3nav/mapdata/render/data.py @@ -100,7 +100,7 @@ class AltitudeAreaGeometries: def __init__(self, altitudearea=None, colors=None): if altitudearea is not None: self.geometry = altitudearea.geometry - self.altitude = altitudearea.altitude + self.altitude = int(altitudearea.altitude * 1000) else: self.geometry = None self.altitude = None @@ -115,15 +115,15 @@ class AltitudeAreaGeometries: for color, areas in self.colors.items()} def create_polyhedrons(self, create_polyhedron, crops): - altitude = float(self.altitude) + altitude = self.altitude self.geometry.build_polyhedron(create_polyhedron, - lower=altitude-0.7, + lower=altitude - int(0.7 * 1000), upper=altitude, crops=crops) for geometry in chain(*(areas.values() for areas in self.colors.values())): geometry.build_polyhedron(create_polyhedron, - lower=altitude-0.1, - upper=altitude+0.001, + lower=altitude - int(0.1 * 1000), + upper=altitude + int(0.001 * 1000), crops=crops) @@ -160,7 +160,7 @@ class LevelRenderData: for area in single_level_geoms[level.pk].altitudeareas: new_coords = np.vstack(tuple(np.array(ring.coords) for ring in get_rings(area.geometry))) coords.append(new_coords) - values.append(np.full((new_coords.shape[0], ), fill_value=float(area.altitude))) + values.append(np.full((new_coords.shape[0], ), fill_value=area.altitude)) last_interpolator = NearestNDInterpolator(np.hstack(coords), np.hstack(values)) @@ -274,8 +274,8 @@ class LevelRenderData: new_geoms.on_top_of_id = old_geoms.on_top_of_id new_geoms.base_altitude = old_geoms.base_altitude new_geoms.default_height = old_geoms.default_height - new_geoms.min_altitude = float(min(area.altitude for area in new_geoms.altitudeareas) - if new_geoms.altitudeareas else new_geoms.base_altitude) + new_geoms.min_altitude = (min(area.altitude for area in new_geoms.altitudeareas) + if new_geoms.altitudeareas else new_geoms.base_altitude) new_geoms.build_mesh(interpolators.get(level.pk) if sublevel.pk == level.pk else None) @@ -324,20 +324,22 @@ class Mesh: __slots__ = ('top', 'sides', 'bottom') def __init__(self, top=None, sides=None, bottom=None): - self.top = np.empty((0, 3, 3), dtype=np.float64) if top is None else top - self.sides = np.empty((0, 3, 3), dtype=np.float64) if sides is None else sides - self.bottom = np.empty((0, 3, 3), dtype=np.float64) if bottom is None else bottom + self.top = np.empty((0, 3, 3), dtype=np.int32) if top is None else top + self.sides = np.empty((0, 3, 3), dtype=np.int32) if sides is None else sides + self.bottom = np.empty((0, 3, 3), dtype=np.int32) if bottom is None else bottom def tolist(self): return self.top, self.sides, self.bottom def __mul__(self, other): - return Mesh(top=self.top*other, - sides=self.sides*other if other[2] != 0 else np.empty((0, 3, 3), dtype=np.float64), - bottom=self.bottom*other) + return Mesh(top=np.rint(self.top*other).astype(np.int32), + sides=np.rint(self.sides*other if other[2] != 0 else np.empty((0, 3, 3))).astype(np.int32), + bottom=np.rint(self.bottom*other).astype(np.int32)) def __add__(self, other): - return Mesh(self.top+other, self.sides+other, self.bottom+other) + return Mesh(np.rint(self.top+other).astype(np.int32), + np.rint(self.sides+other).astype(np.int32), + np.rint(self.bottom+other).astype(np.int32)) def filter(self, top=True, sides=True, bottom=True): return Mesh(top=self.top if top else None, @@ -425,7 +427,7 @@ class LevelGeometries: access_restriction_affected.setdefault(access_restriction, []).append(area.geometry) colors.setdefault(area.get_color(), {}).setdefault(access_restriction, []).append(area.geometry) - heightareas.setdefault(space.height or level.default_height, []).append(space.geometry) + heightareas.setdefault(int((space.height or level.default_height)*1000), []).append(space.geometry) colors.pop(None, None) # merge ground colors @@ -460,10 +462,10 @@ class LevelGeometries: # general level infos geoms.pk = level.pk geoms.on_top_of_id = level.on_top_of_id - geoms.base_altititude = level.base_altitude - geoms.default_height = level.default_height - geoms.min_altitude = float(min(area.altitude for area in geoms.altitudeareas) - if geoms.altitudeareas else level.base_altitude) + geoms.base_altititude = int(level.base_altitude * 1000) + geoms.default_height = int(level.default_height * 1000) + geoms.min_altitude = (min(area.altitude for area in geoms.altitudeareas) + if geoms.altitudeareas else geoms.base_altitude) return geoms @@ -538,17 +540,17 @@ class LevelGeometries: if not edges[last]: edges.pop(last) last = new_last - new_ring = np.array(new_ring, dtype=np.int64) + new_ring = np.array(new_ring, dtype=np.uint32) boundaries.append(tuple(zip(chain((new_ring[-1], ), new_ring), new_ring))) boundaries = np.vstack(boundaries) geom_faces = self.faces[np.array(tuple(chain(*faces)))] if not isinstance(upper, np.ndarray): - upper = np.full(self.vertices.shape[0], fill_value=upper) + upper = np.full(self.vertices.shape[0], fill_value=upper, dtype=np.int32) if not isinstance(lower, np.ndarray): - lower = np.full(self.vertices.shape[0], fill_value=lower) + lower = np.full(self.vertices.shape[0], fill_value=lower, dtype=np.int32) mesh = Mesh() @@ -576,13 +578,13 @@ class LevelGeometries: def build_mesh(self, interpolator=None): rings = tuple(chain(*(get_rings(geom) for geom in self.get_geometries()))) self.vertices, self.faces = triangulate_rings(rings) - self.create_hybrid_geometries(face_centers=self.vertices[self.faces].sum(axis=1) / 3) + self.create_hybrid_geometries(face_centers=self.vertices[self.faces].sum(axis=1) / 3000) # calculate altitudes - vertex_altitudes = self._build_vertex_values((area.geometry, int(area.altitude*100)) - for area in reversed(self.altitudeareas))/100 - vertex_heights = self._build_vertex_values((area, int(height*100)) - for area, height in self.heightareas)/100 + vertex_altitudes = self._build_vertex_values((area.geometry, area.altitude) + for area in reversed(self.altitudeareas)) + vertex_heights = self._build_vertex_values((area, height) + for area, height in self.heightareas) vertex_wall_heights = vertex_altitudes + vertex_heights # create polyhedrons @@ -590,13 +592,14 @@ class LevelGeometries: self.walls_bottom = HybridGeometry(self.walls.geom, self.walls.faces) self.walls.build_polyhedron(self._create_polyhedron, - lower=vertex_altitudes-0.7, + lower=vertex_altitudes - int(0.7 * 1000), upper=vertex_wall_heights) if interpolator is not None: + upper = interpolator(*np.transpose(self.vertices)).astype(np.int32) - int(0.7 * 1000) self.walls_extended.build_polyhedron(self._create_polyhedron, lower=vertex_wall_heights, - upper=interpolator(*np.transpose(self.vertices))-0.2) + upper=upper) else: self.walls_extended = None @@ -609,7 +612,7 @@ class LevelGeometries: self.doors.build_polyhedron(self._create_polyhedron, crops=crops, - lower=vertex_wall_heights-1, + lower=vertex_wall_heights - int(1 * 1000), upper=vertex_wall_heights) for area in self.altitudeareas: @@ -617,14 +620,14 @@ class LevelGeometries: for key, geometry in self.restricted_spaces_indoors.items(): geometry.build_polyhedron(self._create_polyhedron, - lower=vertex_altitudes-0.7, + lower=vertex_altitudes - int(0.7 * 1000), upper=vertex_wall_heights) for key, geometry in self.restricted_spaces_outdoors.items(): geometry.faces = None self.walls_base.build_polyhedron(self._create_polyhedron, - lower=self.min_altitude-0.7, - upper=vertex_altitudes-0.7, + lower=self.min_altitude - int(0.7 * 1000), + upper=vertex_altitudes - int(0.7 * 1000), top=False, bottom=False) self.walls_bottom.build_polyhedron(self._create_polyhedron, lower=0, upper=1, top=False) diff --git a/src/c3nav/mapdata/render/engines/base3d.py b/src/c3nav/mapdata/render/engines/base3d.py index b1670c3c..e7293c09 100644 --- a/src/c3nav/mapdata/render/engines/base3d.py +++ b/src/c3nav/mapdata/render/engines/base3d.py @@ -38,5 +38,7 @@ class Base3DEngine(RenderEngine): mesh.tolist() for mesh in chain(geometry.faces, *geometry.add_faces.values()) )))) if offset: - vertices = vertices * self.np_scale + self.np_offset + vertices = vertices / 1000 * self.np_scale + self.np_offset + else: + vertices = vertices / 1000 return self._append_to_vertices(vertices, append) diff --git a/src/c3nav/mapdata/render/engines/stl.py b/src/c3nav/mapdata/render/engines/stl.py index 6aab4841..fe6fb552 100644 --- a/src/c3nav/mapdata/render/engines/stl.py +++ b/src/c3nav/mapdata/render/engines/stl.py @@ -6,9 +6,9 @@ from c3nav.mapdata.render.engines.base3d import Base3DEngine class STLEngine(Base3DEngine): facet_template = (b' facet normal %f %f %f\n' b' outer loop\n' - b' vertex %f %f %f\n' - b' vertex %f %f %f\n' - b' vertex %f %f %f\n' + b' vertex %.3f %.3f %.3f\n' + b' vertex %.3f %.3f %.3f\n' + b' vertex %.3f %.3f %.3f\n' b' endloop\n' b' endfacet') @@ -17,7 +17,7 @@ class STLEngine(Base3DEngine): def render(self) -> bytes: facets = np.vstack(self.vertices) - facets = np.hstack((np.cross(facets[:, 1]-facets[:, 0], facets[:, 2]-facets[:, 1]).reshape((-1, 1, 3))*1e10, + facets = np.hstack((np.cross(facets[:, 1]-facets[:, 0], facets[:, 2]-facets[:, 1]).reshape((-1, 1, 3))*1e11, facets)) return (b'solid c3nav_export\n' + b'\n'.join((self._create_facet(facet) for facet in facets)) + diff --git a/src/c3nav/mapdata/render/renderer.py b/src/c3nav/mapdata/render/renderer.py index bc7b9265..23f7720f 100644 --- a/src/c3nav/mapdata/render/renderer.py +++ b/src/c3nav/mapdata/render/renderer.py @@ -83,8 +83,8 @@ class MapRenderer: else: levels = self.level_render_data.levels - min_altitude = float(min(chain(*(tuple(area.altitude for area in geoms.altitudeareas) - for geoms in levels)))) - 0.7 + min_altitude = min(chain(*(tuple(area.altitude for area in geoms.altitudeareas) + for geoms in levels))) - int(0.7*1000) for geoms in levels: if not bbox.intersects(geoms.affected_area): @@ -105,8 +105,8 @@ class MapRenderer: offset=min_altitude), fill=FillAttribs('#aaaaaa')) for altitudearea in geoms.altitudeareas: - bottom = float(altitudearea.altitude) - 0.7 - scale = (bottom - min_altitude) / 0.7 + bottom = altitudearea.altitude - int(0.7 * 1000) + scale = (bottom - min_altitude) / int(0.7 * 1000) offset = min_altitude - bottom * scale engine.add_geometry(altitudearea.geometry.fit(scale=scale, offset=offset).filter(top=False), fill=FillAttribs('#aaaaaa')) diff --git a/src/c3nav/mapdata/utils/mesh.py b/src/c3nav/mapdata/utils/mesh.py index 27f253a8..ab75ca11 100644 --- a/src/c3nav/mapdata/utils/mesh.py +++ b/src/c3nav/mapdata/utils/mesh.py @@ -16,10 +16,13 @@ def get_face_indizes(start, length): def triangulate_rings(rings, holes=None): - rings = tuple(tuple(tuple(vertex) for vertex in (np.array(ring.coords)*1000).astype(np.uint64)) for ring in rings) + rings = tuple( + tuple(tuple(vertex) for vertex in np.rint(np.array(ring.coords)*1000).astype(np.int32)) + for ring in rings + ) if not rings: - return np.empty((0, 2), dtype=np.float32), np.empty((0, 3), dtype=np.int32) + return np.empty((0, 2), dtype=np.int32), np.empty((0, 3), dtype=np.uint32) vertices = tuple(set(chain(*rings))) vertices_lookup = {vertex: i for i, vertex in enumerate(vertices)} @@ -38,7 +41,7 @@ def triangulate_rings(rings, holes=None): info.set_holes(holes) mesh = triangle.build(info, quality_meshing=False) - return np.array(mesh.points)/1000, np.array(mesh.elements) + return np.rint(np.array(mesh.points)).astype(np.int32), np.array(mesh.elements, dtype=np.uint32) def _triangulate_polygon(polygon: Polygon, keep_holes=False):