feat: tessellation of multiple multi-triangle faces
This commit is contained in:
parent
c51dcd6514
commit
4f0d3ce83d
1 changed files with 76 additions and 38 deletions
96
map.py
96
map.py
|
@ -4,6 +4,8 @@ import numpy as np
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from tqdm import tqdm, trange
|
||||||
|
|
||||||
|
|
||||||
EPS = sys.float_info.epsilon
|
EPS = sys.float_info.epsilon
|
||||||
|
|
||||||
|
@ -32,23 +34,30 @@ class Triangle:
|
||||||
self.v3p = self.plane_coords(self.v3)
|
self.v3p = self.plane_coords(self.v3)
|
||||||
|
|
||||||
def intersect(self, ray):
|
def intersect(self, ray):
|
||||||
|
'''
|
||||||
|
:param ray: numpy.array(3) Ray cast from [0, 0, 0]
|
||||||
|
:return: A tuple of [bool, Optional[numpy.array(3)]]:
|
||||||
|
The first component is True if the ray intersects the triangle, False otherwise.
|
||||||
|
The second component marks the intersection of the triangle's plane (even if outside the triangle), or
|
||||||
|
None if the ray does not intersect the plane at all.
|
||||||
|
'''
|
||||||
# https://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm
|
# https://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm
|
||||||
ray_cross_e2 = np.cross(ray, self.e2)
|
ray_cross_e2 = np.cross(ray, self.e2)
|
||||||
dot = np.dot(self.e1, ray_cross_e2)
|
dot = np.dot(self.e1, ray_cross_e2)
|
||||||
if abs(dot) < EPS:
|
if abs(dot) < EPS:
|
||||||
return None
|
return False, None
|
||||||
s = -self.v1
|
s = -self.v1
|
||||||
u = np.dot(s, ray_cross_e2) / dot
|
u = np.dot(s, ray_cross_e2) / dot
|
||||||
if (u < 0 and abs(u) > EPS) or (u > 1 and abs(u - 1) > EPS):
|
|
||||||
return None
|
|
||||||
s_cross_e1 = np.cross(s, self.e1)
|
s_cross_e1 = np.cross(s, self.e1)
|
||||||
v = np.dot(ray, s_cross_e1) / dot
|
v = np.dot(ray, s_cross_e1) / dot
|
||||||
if (v < 0 and abs(v) > EPS) or (u + v > 1 and abs(u + v - 1) > EPS):
|
|
||||||
return None
|
|
||||||
t = np.dot(self.e2, s_cross_e1) / dot
|
t = np.dot(self.e2, s_cross_e1) / dot
|
||||||
|
if (u < 0 and abs(u) > EPS) or (u > 1 and abs(u - 1) > EPS):
|
||||||
|
return False, ray * t
|
||||||
|
if (v < 0 and abs(v) > EPS) or (u + v > 1 and abs(u + v - 1) > EPS):
|
||||||
|
return False, ray * t
|
||||||
if t <= EPS:
|
if t <= EPS:
|
||||||
return None
|
return False, None
|
||||||
return ray * t
|
return True, ray * t
|
||||||
|
|
||||||
def plane_coords(self, v):
|
def plane_coords(self, v):
|
||||||
u = np.dot(self.e1 / self.e1n, v - self.v1)
|
u = np.dot(self.e1 / self.e1n, v - self.v1)
|
||||||
|
@ -57,6 +66,16 @@ class Triangle:
|
||||||
return np.array([u, v])
|
return np.array([u, v])
|
||||||
|
|
||||||
|
|
||||||
|
class Face:
|
||||||
|
|
||||||
|
def __init__(self, vs):
|
||||||
|
self.vertices = vs
|
||||||
|
self.tris = []
|
||||||
|
for i in range(1, len(vs)-1):
|
||||||
|
self.tris.append(Triangle(vs[0], vs[i], vs[i+1]))
|
||||||
|
self.plane_coords = [self.tris[0].plane_coords(v) for v in self.vertices]
|
||||||
|
|
||||||
|
|
||||||
def convert_to_cartesian(lon, lat):
|
def convert_to_cartesian(lon, lat):
|
||||||
x = cos(lon*pi/180)
|
x = cos(lon*pi/180)
|
||||||
y = sin(lon*pi/180)
|
y = sin(lon*pi/180)
|
||||||
|
@ -65,52 +84,71 @@ def convert_to_cartesian(lon, lat):
|
||||||
return np.array([x/mag, y/mag, z/mag])
|
return np.array([x/mag, y/mag, z/mag])
|
||||||
|
|
||||||
|
|
||||||
def map_poly(tri, poly):
|
def map_poly(tri, poly, ref=None):
|
||||||
|
if ref is None:
|
||||||
|
ref = tri
|
||||||
p0 = convert_to_cartesian(*poly[0])
|
p0 = convert_to_cartesian(*poly[0])
|
||||||
p0i = tri.intersect(p0)
|
p0b, p0i = tri.intersect(p0)
|
||||||
mapped = []
|
mapped = []
|
||||||
new = True
|
new = True
|
||||||
for i in range(1, len(poly)):
|
for i in range(1, len(poly)):
|
||||||
p1 = convert_to_cartesian(*poly[i])
|
p1 = convert_to_cartesian(*poly[i])
|
||||||
p1i = tri.intersect(p1)
|
p1b, p1i = tri.intersect(p1)
|
||||||
if p0i is not None and p1i is not None:
|
if p0b or p1b and p0i is not None and p1i is not None:
|
||||||
if new:
|
if new:
|
||||||
mapped.append([tri.plane_coords(p0i), tri.plane_coords(p1i)])
|
mapped.append([ref.plane_coords(p0i), ref.plane_coords(p1i)])
|
||||||
new = False
|
new = False
|
||||||
else:
|
else:
|
||||||
mapped[-1].append(tri.plane_coords(p1i))
|
mapped[-1].append(ref.plane_coords(p1i))
|
||||||
else:
|
else:
|
||||||
new = True
|
new = True
|
||||||
p0 = p1
|
p0 = p1
|
||||||
p0i = p1i
|
p0i = p1i
|
||||||
|
p0b = p1b
|
||||||
return mapped
|
return mapped
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
t = Triangle(np.array([5,0,0]), np.array([0,5,0]), np.array([0,0,5]))
|
fs = [Face([a, b, c, d]) for a, b, c, d in [
|
||||||
countries = {}
|
(np.array([1,1,1]), np.array([-1,1,1]), np.array([-1,-1,1]), np.array([1,-1,1])), # top
|
||||||
|
(np.array([1,1,-1]), np.array([-1,1,-1]), np.array([-1,-1,-1]), np.array([1,-1,-1])), # bottom
|
||||||
|
(np.array([-1,-1,-1]), np.array([-1,1,-1]), np.array([-1,1,1]), np.array([-1,-1,1])), # left
|
||||||
|
(np.array([1,-1,-1]), np.array([1,1,-1]), np.array([1,1,1]), np.array([1,-1,1])), # right
|
||||||
|
(np.array([-1,-1,-1]), np.array([1,-1,-1]), np.array([1,-1,1]), np.array([-1,-1,1])), # front
|
||||||
|
(np.array([-1,1,-1]), np.array([1,1,-1]), np.array([1,1,1]), np.array([-1,1,1])), # back
|
||||||
|
]]
|
||||||
with open('countries.geojson', 'r') as f:
|
with open('countries.geojson', 'r') as f:
|
||||||
j = json.load(f)
|
j = json.load(f)
|
||||||
for f in j['features']:
|
|
||||||
|
for i, face in tqdm(list(enumerate(fs)), desc='Faces '):
|
||||||
|
countries = {}
|
||||||
|
for t in face.tris:
|
||||||
|
for f in tqdm(j['features'], desc='Features', total=len(face.tris)*len(j['features'])):
|
||||||
cc = f['properties']['ADMIN']
|
cc = f['properties']['ADMIN']
|
||||||
if f['geometry']['type'] == 'MultiPolygon':
|
if f['geometry']['type'] == 'MultiPolygon':
|
||||||
countries[cc] = []
|
|
||||||
for poly in f['geometry']['coordinates']:
|
for poly in f['geometry']['coordinates']:
|
||||||
countries[cc].extend(map_poly(t, poly[0]))
|
countries.setdefault(cc, []).extend(map_poly(t, poly[0], face.tris[0]))
|
||||||
elif f['geometry']['type'] == 'Polygon':
|
elif f['geometry']['type'] == 'Polygon':
|
||||||
countries[cc] = map_poly(tri, f['geometry']['coordinates'][0])
|
countries.setdefault(cc, []).extend(map_poly(t, f['geometry']['coordinates'][0], face.tris[0]))
|
||||||
minx = min(t.v1p[0], t.v2p[0], t.v3p[0])
|
minx = min(v[0] for v in face.plane_coords)
|
||||||
miny = min(t.v1p[1], t.v2p[1], t.v3p[1])
|
miny = min(v[1] for v in face.plane_coords)
|
||||||
maxx = max(t.v1p[0], t.v2p[0], t.v3p[0])
|
maxx = max(v[0] for v in face.plane_coords)
|
||||||
maxy = max(t.v1p[1], t.v2p[1], t.v3p[1])
|
maxy = max(v[1] for v in face.plane_coords)
|
||||||
w = maxx - minx
|
w = maxx - minx
|
||||||
h = maxy - miny
|
h = maxy - miny
|
||||||
print(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{minx} {miny} {w} {h}" width="{w}" height="{h}">')
|
with open(f'face{i}.svg', 'w') as svg:
|
||||||
print(f'<path fill="none" stroke="black" stroke-width="0.01" d="M {t.v1p[0]} {t.v1p[1]} L {t.v2p[0]} {t.v2p[1]} L {t.v3p[0]} {t.v3p[1]} Z" />')
|
svg.write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{minx} {miny} {w} {h}" width="{w}" height="{h}" id="face{i}">\n')
|
||||||
|
svg.write(f' <g id="cut">\n <path id="outline" fill="none" stroke="black" stroke-width="0.01" d="M {face.plane_coords[0][0]} {face.plane_coords[0][1]}')
|
||||||
|
for v in face.plane_coords[1:]:
|
||||||
|
svg.write(f' L {v[0]} {v[1]}')
|
||||||
|
svg.write(' Z" />\n </g>\n <g id="engrave">\n')
|
||||||
for c, l in countries.items():
|
for c, l in countries.items():
|
||||||
|
if l:
|
||||||
|
svg.write(f' <g id="{c}">\n')
|
||||||
for ls in l:
|
for ls in l:
|
||||||
print(f'<path fill="none" stroke="red" stroke-width="0.01" d="M {ls[0][0]} {ls[0][1]}', end='')
|
svg.write(f' <path fill="none" stroke="red" stroke-width="0.01" d="M {ls[0][0]} {ls[0][1]}')
|
||||||
for x, y in ls[1:]:
|
for x, y in ls[1:]:
|
||||||
print(f' L {x} {y}', end='')
|
svg.write(f' L {x} {y}')
|
||||||
print('" />')
|
svg.write('" />\n')
|
||||||
print('</svg>')
|
svg.write(' </g>\n')
|
||||||
|
svg.write(' </g>\n</svg>\n')
|
||||||
|
|
Loading…
Reference in a new issue