def _pyramidal_top(ac_object: ac.Object, x_centre: float, y_centre: float, top: float, roof_texture, roof_mat_idx: int, pyramid_height: float, ring: List[List[float]], object_node_index: int) -> None: """Adds the pyramidal top by adding the centre node and the necessary faces. ring is a list of x,y coordinates for the current top points of the roof. """ # add node for the middle of the roof ac_object.node(-1 * y_centre, top, -1 * x_centre) roof_texture_size = roof_texture.h_size_meters # loop on sides of the building n_pts = len(ring) # number of points for i in range(0, n_pts): dist_inwards = coord.calc_distance_local(ring[i][0], ring[i][1], x_centre, y_centre) j = (i+1) % n_pts dist_edge = coord.calc_distance_local(ring[i][0], ring[i][1], ring[j][0], ring[j][1]) len_roof_hypo = (dist_inwards ** 2 + pyramid_height ** 2) ** 0.5 repeat_x = dist_edge/roof_texture_size repeat_y = len_roof_hypo/roof_texture_size ac_object.face([(object_node_index + i, roof_texture.x(0), roof_texture.y(0)), (object_node_index + j, roof_texture.x(repeat_x), roof_texture.y(0)), (object_node_index + n_pts, roof_texture.x(0.5*repeat_x), roof_texture.y(repeat_y))], mat_idx=roof_mat_idx)
def _write_quads(self, obj: ac3d.Object, left_nodes_list, right_nodes_list, tex_y0, tex_y1, check_lit: bool = False) -> None: """Write a series of quads bound by left and right. Left/right are lists of node indices which will be used to form a series of quads. Material index tells whether it is lit or not. """ n_nodes = len(left_nodes_list) assert (len(left_nodes_list) == len(right_nodes_list)) for i in range(n_nodes - 1): mat_idx = mat.Material.unlit if check_lit: if self.lighting[i] or self.lighting[i + 1]: mat_idx = mat.Material.lit xl = self.dist[i] / road.LENGTH xr = self.dist[i + 1] / road.LENGTH face = [(left_nodes_list[i], xl, tex_y0), (left_nodes_list[i + 1], xr, tex_y0), (right_nodes_list[i + 1], xr, tex_y1), (right_nodes_list[i], xl, tex_y1)] obj.face(face[::-1], mat_idx=mat_idx.value)
def separate_skillion(ac_object: ac.Object, b, roof_mat_idx: int): """skillion roof, n nodes, separate model. Inward_""" # - handle square skillion roof # it's assumed that the first 2 nodes are at building:height-roof:height # the last 2 nodes are at building:height # -- 4 corners object_node_index = ac_object.next_node_index() for x in b.pts_all: ac_object.node(-x[1], b.beginning_of_roof_above_sea_level, -x[0]) # We don't want the hipped part to be greater than the height, which is 45 deg # FLAT PART i = 0 for pt in b.pts_all: ac_object.node(-pt[1], b.beginning_of_roof_above_sea_level + b.roof_height_pts[i], -pt[0]) i += 1 if b.polygon.interiors: print(" len(b.polygon.interiors)") outer_closest = copy.copy(b.outer_nodes_closest) print(("outer_closest = copy.copy(b.outer_nodes_closest)", outer_closest)) i = b.pts_outer_count inner_ring = 0 nodes = [] for object_node_index in range(b.pts_outer_count): nodes.append(object_node_index) if outer_closest and object_node_index == outer_closest[0]: len_ring = len(b.polygon.interiors[inner_ring].coords) - 1 a = np.arange(len_ring) + i for x in a: nodes.append(x) nodes.append(a[0]) # -- close inner ring i += len_ring inner_ring += 1 outer_closest.pop(0) nodes.append(object_node_index) # -- go back to outer ring else: nodes = list(range(b.pts_outer_count)) uv = face_uv(nodes, b.pts_all, b.roof_texture, angle=None) assert(len(nodes) == b.pts_all_count + 2 * len(b.polygon.interiors)) nodes_uv_list = [] object_node_index = ac_object.next_node_index() # create nodes for/ and roof for i, node in enumerate(nodes): # new nodes ac_object.node(-b.pts_all[node][1], b.beginning_of_roof_above_sea_level + b.roof_height_pts[node], -b.pts_all[node][0]) nodes_uv_list.append((object_node_index + node, uv[i][0], uv[i][1])) ac_object.face(nodes_uv_list, mat_idx=roof_mat_idx) return
def flat(ac_object: ac.Object, index_first_node_in_ac_obj: int, b, roof_mgr: RoofManager, roof_mat_idx: int, stats: Stats) -> None: """Flat roof. Also works for relations.""" # 3-----------------2 Outer is CCW: 0 1 2 3 # | /| Inner[0] is CW: 4 5 6 7 # | 11----8 | Inner[1] is CW: 8 9 10 11 # | 5----6 | | | Inner rings are rolled such that their first nodes # | | | 10-<<-9 | are closest to an outer node. (see b.roll_inner_nodes) # | 4-<<-7 | # |/ | draw 0 - 4 5 6 7 4 - 0 1 2 - 8 9 10 11 8 - 2 3 # 0---->>-----------1 if b.roof_texture is None: raise ValueError("Roof texture None") if "compat:roof-flat" not in b.roof_requires: # flat roof might have gotten required later in process, so we must find a new roof texture logging.debug("Replacing texture for flat roof despite " + str(b.roof_requires)) if "compat:roof-pitched" in b.roof_requires: b.roof_requires.remove("compat:roof-pitched") b.roof_requires.append("compat:roof-flat") b.roof_texture = roof_mgr.find_matching_roof(b.roof_requires, b.longest_edge_length, stats) if b.polygon.interiors: outer_closest = copy.copy(b.outer_nodes_closest) i = b.pts_outer_count inner_ring = 0 nodes = [] for o in range(b.pts_outer_count): nodes.append(o) if outer_closest and o == outer_closest[0]: len_ring = len(b.polygon.interiors[inner_ring].coords) - 1 a = np.arange(len_ring) + i for x in a: nodes.append(x) nodes.append(a[0]) # -- close inner ring i += len_ring inner_ring += 1 outer_closest.pop(0) nodes.append(o) # -- go back to outer ring else: nodes = list(range(b.pts_outer_count)) uv = _face_uv_flat_roof(nodes, b.pts_all, b.roof_texture) nodes = np.array(nodes) + b.pts_all_count assert(len(nodes) == b.pts_all_count + 2 * len(b.polygon.interiors)) nodes_uv_list = [] for i, node in enumerate(nodes): nodes_uv_list.append((node + index_first_node_in_ac_obj, uv[i][0], uv[i][1])) ac_object.face(nodes_uv_list, mat_idx=roof_mat_idx)
def _write_pier_area(self, obj: ac3d.Object, offset: co.Vec2d) -> None: """Writes a Pier mapped as an area""" if len(self.nodes) < 3: logging.debug( 'ERROR: platform with osm_id=%d cannot created due to less then 3 nodes', self.osm_id) return linear_ring = shg.LinearRing(self.nodes) # TODO shg.LinearRing().is_ccw o = obj.next_node_index() if linear_ring.is_ccw: logging.info('CounterClockWise') else: # normalize to CCW logging.info("Clockwise") self.nodes = self.nodes[::-1] # top ring e = self.elevation + 1 for p in self.nodes: obj.node(-p[1] + offset.y, e, -p[0] + offset.x) top_nodes = np.arange(len(self.nodes)) self.segment_len = np.array([0] + [ co.Vec2d(coord).distance_to(co.Vec2d(linear_ring.coords[i])) for i, coord in enumerate(linear_ring.coords[1:]) ]) rd_len = len(linear_ring.coords) self.dist = np.zeros((rd_len)) for i in range(1, rd_len): self.dist[i] = self.dist[i - 1] + self.segment_len[i] face = [] x = 0. # reversed(list(enumerate(a))) # Top Face for i, n in enumerate(top_nodes): face.append((n + o, x, 0.5)) obj.face(face) # Build bottom ring e = self.elevation - 5 for p in self.nodes: obj.node(-p[1] + offset.y, e, -p[0] + offset.x) # Build Sides for i, n in enumerate(top_nodes[1:]): sideface = list() sideface.append((n + o + rd_len - 1, x, 0.5)) sideface.append((n + o + rd_len, x, 0.5)) sideface.append((n + o, x, 0.5)) sideface.append((n + o - 1, x, 0.5)) obj.face(sideface)
def _write_nodes(self, obj: ac3d.Object, line_string: shg.LineString, z, cluster_elev: float, offset: Optional[co.Vec2d] = None, join: bool = False, is_left: bool = False) -> List[int]: """given a LineString and z, write nodes to .ac. Return nodes_list """ to_write = copy.copy(line_string.coords) nodes_list = [] assert (self.cluster_ref is not None) if not join: nodes_list += list(obj.next_node_index() + np.arange(len(to_write))) else: if len(self.junction0) == 2: try: # if other node already exists, do not write a new one other_node = self.junction0.get_other_node( self, is_left, self.cluster_ref) # other nodes already written: to_write = to_write[1:] z = z[1:] nodes_list.append(other_node) except KeyError: self.junction0.set_other_node(self, is_left, obj.next_node_index(), self.cluster_ref) # -- make list with all but last node -- we might add last node later nodes_list += list(obj.next_node_index() + np.arange(len(to_write) - 1)) last_node = obj.next_node_index() + len(to_write) - 1 if len(self.junction1) == 2: try: # if other node already exists, do not write a new one other_node = self.junction1.get_other_node( self, is_left, self.cluster_ref) # other nodes already written: to_write = to_write[:-1] z = z[:-1] nodes_list.append(other_node) except KeyError: self.junction1.set_other_node(self, is_left, last_node, self.cluster_ref) nodes_list.append(last_node) else: nodes_list.append(last_node) for i, the_node in enumerate(to_write): e = z[i] - cluster_elev obj.node(-(the_node[1] - offset.y), e, -(the_node[0] - offset.x)) return nodes_list
def _write_area(self, fg_elev: utilities.FGElev, obj: ac3d.Object, offset: co.Vec2d) -> None: """Writes a platform mapped as an area""" if len(self.nodes) < 3: logging.debug('ERROR: platform with osm_id=%d cannot created due to less then 3 nodes', self.osm_id) return linear_ring = shg.LinearRing(self.nodes) o = obj.next_node_index() if linear_ring.is_ccw: logging.debug('Anti-Clockwise') else: logging.debug("Clockwise") self.nodes = self.nodes[::-1] for p in self.nodes: e = fg_elev.probe_elev((p[0], p[1])) + 1 obj.node(-p[1] + offset.y, e, -p[0] + offset.x) top_nodes = np.arange(len(self.nodes)) self.segment_len = np.array([0] + [co.Vec2d(coord).distance_to(co.Vec2d(self.line_string.coords[i])) for i, coord in enumerate(self.line_string.coords[1:])]) rd_len = len(self.line_string.coords) self.dist = np.zeros((rd_len)) for i in range(1, rd_len): self.dist[i] = self.dist[i - 1] + self.segment_len[i] face = [] x = 0. # Top Face for i, n in enumerate(top_nodes): face.append((n + o, x, 0.5)) obj.face(face) # Build bottom ring for p in self.nodes: e = fg_elev.probe_elev((p[0], p[1])) - 1 obj.node(-p[1] + offset.y, e, -p[0] + offset.x) # Build Sides for i, n in enumerate(top_nodes[1:]): sideface = list() sideface.append((n + o + rd_len - 1, x, 0.5)) sideface.append((n + o + rd_len, x, 0.5)) sideface.append((n + o, x, 0.5)) sideface.append((n + o - 1, x, 0.5)) obj.face(sideface)
def _pillar(self, obj: ac3d.Object, x, y, h0, h1, angle): self.pillar_r0 = 1. self.pillar_r1 = 0.5 self.pillar_nnodes = 8 rx = self.pillar_r0 ry = self.pillar_r1 nodes_list = [] ofs = obj.next_node_index() vert = "" r = np.array([[np.cos(-angle), -np.sin(-angle)], [np.sin(-angle), np.cos(-angle)]]) for a in np.linspace(0, 2 * np.pi, self.pillar_nnodes, endpoint=False): a += np.pi / self.pillar_nnodes node = np.array([rx * np.cos(a), ry * np.sin(a)]) node = np.dot(r, node) obj.node(-(y + node[0]), h1, -(x + node[1])) for a in np.linspace(0, 2 * np.pi, self.pillar_nnodes, endpoint=False): a += np.pi / self.pillar_nnodes node = np.array([rx * np.cos(a), ry * np.sin(a)]) node = np.dot(r, node) obj.node(-(y + node[0]), h0, -(x + node[1])) for i in range(self.pillar_nnodes - 1): face = [(ofs + i, 0, road.BOTTOM[0]), (ofs + i + 1, 1, road.BOTTOM[0]), (ofs + i + 1 + self.pillar_nnodes, 1, road.BOTTOM[1]), (ofs + i + self.pillar_nnodes, 0, road.BOTTOM[1])] obj.face(face) i = self.pillar_nnodes - 1 face = [(ofs + i, 0, road.BOTTOM[0]), (ofs, 1, road.BOTTOM[0]), (ofs + self.pillar_nnodes, 1, road.BOTTOM[1]), (ofs + i + self.pillar_nnodes, 0, road.BOTTOM[1])] obj.face(face) nodes_list.append(face) return ofs + 2 * self.pillar_nnodes, vert, nodes_list
def write_to(self, obj: ac3d.Object, fg_elev: FGElev, elev_offset, offset=None) -> None: """ write - deck - sides - bottom - pillars needs - pillar positions - deck elev - """ n_nodes = len(self.left.coords) # -- deck height z = np.zeros(n_nodes) length = 0. for i in range(n_nodes): z[i] = self._deck_height(length, normalized=False) node = self.nodes_dict[self.way.refs[i]] layer = node.layer_for_way(self.way) if layer > 0: z[i] += layer * parameters.DISTANCE_BETWEEN_LAYERS z[i] += parameters.MIN_ABOVE_GROUND_LEVEL length += self.segment_len[i] left_top_nodes = self._write_nodes(obj, self.left, z, elev_offset, offset, join=True, is_left=True) right_top_nodes = self._write_nodes(obj, self.right, z, elev_offset, offset, join=True, is_left=False) left_bottom_edge, right_bottom_edge = self._compute_sides(self.width / 2 * 0.85) left_bottom_nodes = self._write_nodes( obj, left_bottom_edge, z - parameters.BRIDGE_BODY_HEIGHT, elev_offset, offset) right_bottom_nodes = self._write_nodes( obj, right_bottom_edge, z - parameters.BRIDGE_BODY_HEIGHT, elev_offset, offset) # -- top self._write_quads(obj, left_top_nodes, right_top_nodes, self.tex[0], self.tex[1], True) # -- right self._write_quads(obj, right_top_nodes, right_bottom_nodes, road.BRIDGE_1[1], road.BRIDGE_1[0]) # -- left self._write_quads(obj, left_bottom_nodes, left_top_nodes, road.BRIDGE_1[0], road.BRIDGE_1[1]) # -- bottom self._write_quads(obj, right_bottom_nodes, left_bottom_nodes, road.BOTTOM[0], road.BOTTOM[1]) # -- end wall 1 the_node = self.left.coords[0] e = fg_elev.probe_elev(the_node) - elev_offset left_bottom_node = obj.node(-(the_node[1] - offset.y), e, -(the_node[0] - offset.x)) the_node = self.right.coords[0] e = fg_elev.probe_elev(the_node) - elev_offset right_bottom_node = obj.node(-(the_node[1] - offset.y), e, -(the_node[0] - offset.x)) face = [(left_top_nodes[0], 0, parameters.EMBANKMENT_TEXTURE[0]), (right_top_nodes[0], 0, parameters.EMBANKMENT_TEXTURE[1]), (right_bottom_node, 1, parameters.EMBANKMENT_TEXTURE[1]), (left_bottom_node, 1, parameters.EMBANKMENT_TEXTURE[0])] obj.face(face) # -- end wall 2 the_node = self.left.coords[-1] e = fg_elev.probe_elev(the_node) - elev_offset left_bottom_node = obj.node(-(the_node[1] - offset.y), e, -(the_node[0] - offset.x)) the_node = self.right.coords[-1] e = fg_elev.probe_elev(the_node) - elev_offset right_bottom_node = obj.node(-(the_node[1] - offset.y), e, -(the_node[0] - offset.x)) face = [(left_top_nodes[-1], 0, parameters.EMBANKMENT_TEXTURE[0]), (right_top_nodes[-1], 0, parameters.EMBANKMENT_TEXTURE[1]), (right_bottom_node, 1, parameters.EMBANKMENT_TEXTURE[1]), (left_bottom_node, 1, parameters.EMBANKMENT_TEXTURE[0])] obj.face(face[::-1]) # pillars z -= elev_offset for i in range(1, n_nodes - 1): z0 = fg_elev.probe_elev(self.center.coords[i]) - elev_offset - 1. point = self.center.coords[i] self._pillar(obj, point[0] - offset.x, point[1] - offset.y, z0, z[i], self.angle[i])
def separate_pyramidal(ac_object: ac.Object, b, roof_mat_idx: int) -> None: """Pyramidal, dome or onion roof.""" shape = b.roof_shape roof_texture = b.roof_texture roof_height = sanity_roof_height_complex(b, 'pyramidal') bottom = b.beginning_of_roof_above_sea_level # add nodes for each of the corners object_node_index = ac_object.next_node_index() prev_ring = list() for pt in b.pts_all: ac_object.node(-pt[1], bottom, -pt[0]) prev_ring.append([pt[0], pt[1]]) # calculate node for the middle node of the roof x_centre = sum([xi[0] for xi in b.pts_all])/len(b.pts_all) y_centre = sum([xi[1] for xi in b.pts_all])/len(b.pts_all) ring = b.pts_all top = bottom + roof_height if shape in [enu.RoofShape.dome, enu.RoofShape.onion]: # For dome and onion we need to add new rings and faces before the top height_share = list() # the share of the roof height by each ring radius_share = list() # the share of the radius by each ring if shape is enu.RoofShape.dome: # we use five additional rings height_share = [sin(radians(90 / 6)), sin(radians(90 * 2 / 6)), sin(radians(90 * 3 / 6)), sin(radians(90 * 4 / 6)), sin(radians(90 * 5 / 6))] radius_share = [cos(radians(90 / 6)), cos(radians(90 * 2 / 6)), cos(radians(90 * 3 / 6)), cos(radians(90 * 4 / 6)), cos(radians(90 * 5 / 6))] else: # we use five additional rings based on guessed values - onion diameter gets broader than drum height_share = [.1, .2, .3, .4, .5, .7] radius_share = [1.2, 1.25, 1.2, 1., .6, .2] # texture it roof_texture_size = roof_texture.h_size_meters # size of roof texture in meters n_pts = len(ring) for r in range(0, len(height_share)): ring = list() top = bottom + roof_height * height_share[r] # calculate the new points of the ring for pt in b.pts_all: x, y = coord.calc_point_on_line_local(pt[0], pt[1], x_centre, y_centre, 1 - radius_share[r]) ac_object.node(-y, top, -x) ring.append([x, y]) # create the faces prev_offset = r * n_pts this_offset = (r+1) * n_pts for i in range(0, n_pts): j = (i + 1) % n_pts # little trick to reset to 0 dist_edge = coord.calc_distance_local(ring[i][0], ring[i][1], ring[j][0], ring[j][1]) dist_edge_prev = coord.calc_distance_local(prev_ring[i][0], prev_ring[i][1], prev_ring[j][0], prev_ring[j][1]) ring_height_diff = height_share[r] * roof_height ring_radius_diff = coord.calc_distance_local(ring[i][0], ring[i][1], prev_ring[i][0], prev_ring[i][1]) len_roof_hypo = (ring_height_diff ** 2 + ring_radius_diff ** 2) ** 0.5 repeat_x = dist_edge / roof_texture_size repeat_y = len_roof_hypo / roof_texture_size ac_object.face([(object_node_index + i + prev_offset, roof_texture.x(0), roof_texture.y(0)), (object_node_index + j + prev_offset, roof_texture.x(repeat_x), roof_texture.y(0)), (object_node_index + j + this_offset, roof_texture.x(repeat_x), roof_texture.y(repeat_y)), (object_node_index + i + this_offset, roof_texture.x(0), roof_texture.y(repeat_y))], mat_idx=roof_mat_idx) prev_ring = copy.deepcopy(ring) # prepare for pyramidal top top = bottom + roof_height bottom = bottom + roof_height * height_share[-1] object_node_index += len(height_share) * n_pts # add the pyramidal top _pyramidal_top(ac_object, x_centre, y_centre, top, roof_texture, roof_mat_idx, top - bottom, ring, object_node_index)