You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
5.2 KiB
155 lines
5.2 KiB
import sys
|
|
import pathlib
|
|
|
|
import numpy
|
|
import scipy.integrate
|
|
import scipy.optimize
|
|
import scipy.interpolate
|
|
|
|
|
|
def large_arch(fc=60, Qb=100, Qt=49, L=30, nsegs=25):
|
|
"""
|
|
http://en.wikipedia.org/wiki/Jefferson_National_Expansion_Memorial
|
|
fc = maximum height of centroid (in feet) = 625.0925
|
|
Qb = maximum cross sectional area of arch at base (in sq. feet) = 1262.6651
|
|
Qt = minimum cross sectional area of arch at top (in sq. feet) = 125.1406
|
|
L = half width of centroid at the base (in feet) = 299.2239
|
|
for our scene, in cm
|
|
"""
|
|
|
|
A = fc / (Qb / Qt - 1)
|
|
C = numpy.arccosh(Qb / Qt)
|
|
x0 = -L
|
|
|
|
#################################################################
|
|
# Arch function
|
|
#################################################################
|
|
def arch(x: numpy.ndarray):
|
|
return -A * (numpy.cosh(C * x / L) - 1) + fc
|
|
|
|
#################################################################
|
|
# Arch derivative function
|
|
#################################################################
|
|
def darch(x: numpy.ndarray):
|
|
return -A * numpy.sinh(C * x / L) * C / L
|
|
|
|
#################################################################
|
|
# Arc-length function
|
|
#################################################################
|
|
def arc_len_func(x: numpy.ndarray):
|
|
return numpy.sqrt(
|
|
1 + A**2 * (numpy.cosh(C * x / L)**2 - 1) * C**2 / L**2)
|
|
|
|
arc_len = scipy.integrate.quad(arc_len_func, -L, L)[0]
|
|
|
|
#################################################################
|
|
# Function used to indicate when equal arc-length
|
|
# has been reached
|
|
#################################################################
|
|
def eq_arc_len_func(x: numpy.ndarray):
|
|
return scipy.integrate.quad(arc_len_func, x0, x)[0] - arc_len / nsegs
|
|
|
|
p = numpy.zeros((nsegs * 8, 3))
|
|
k = 0
|
|
|
|
while x0 < L * 0.999:
|
|
# next segment
|
|
x1 = scipy.optimize.fsolve(eq_arc_len_func, x0)[0]
|
|
y0 = arch(x0)
|
|
y1 = arch(x1)
|
|
# normals
|
|
dydx0 = darch(x0)
|
|
dydx1 = darch(x1)
|
|
v0 = numpy.array([-dydx0, 1])
|
|
v1 = numpy.array([-dydx1, 1])
|
|
v0 /= numpy.linalg.norm(v0)
|
|
v1 /= numpy.linalg.norm(v1)
|
|
# block width depends on the height
|
|
a0 = y0 / fc
|
|
a1 = y1 / fc
|
|
a0 = max(min(a0, 1), 0)
|
|
a1 = max(min(a1, 1), 0)
|
|
w = scipy.interpolate.interp1d(
|
|
[0, 1], [numpy.sqrt(Qb), numpy.sqrt(Qt)])
|
|
w0 = w(a0)
|
|
w1 = w(a1)
|
|
# hack to make faces not exactly the same size
|
|
if x0 < 0:
|
|
w1 = w0
|
|
else:
|
|
w0 = w1
|
|
# block corners
|
|
v0 = 0.5 * w0 * v0
|
|
v1 = 0.5 * w1 * v1
|
|
p000 = numpy.array([x0 - v0[0], y0 - v0[1], -0.5 * w0])
|
|
p100 = numpy.array([x0 + v0[0], y0 + v0[1], -0.5 * w0])
|
|
p010 = numpy.array([x1 - v1[0], y1 - v1[1], -0.5 * w1])
|
|
p110 = numpy.array([x1 + v1[0], y1 + v1[1], -0.5 * w1])
|
|
p001 = numpy.array([x0 - v0[0], y0 - v0[1], 0.5 * w0])
|
|
p101 = numpy.array([x0 + v0[0], y0 + v0[1], 0.5 * w0])
|
|
p011 = numpy.array([x1 - v1[0], y1 - v1[1], 0.5 * w1])
|
|
p111 = numpy.array([x1 + v1[0], y1 + v1[1], 0.5 * w1])
|
|
p[k * 8 + 0] = p000.ravel()
|
|
p[k * 8 + 1] = p001.ravel()
|
|
p[k * 8 + 2] = p010.ravel()
|
|
p[k * 8 + 3] = p011.ravel()
|
|
p[k * 8 + 4] = p100.ravel()
|
|
p[k * 8 + 5] = p101.ravel()
|
|
p[k * 8 + 6] = p110.ravel()
|
|
p[k * 8 + 7] = p111.ravel()
|
|
# Add a small gap between pieces
|
|
p[k * 8:(k + 1) * 8, 0] += (k - nsegs // 2) * 0.1
|
|
p[k * 8:(k + 1) * 8, 1] += (nsegs // 2 * 0.1 -
|
|
abs((k - nsegs // 2) * 0.1))
|
|
x0 = x1
|
|
k += 1
|
|
|
|
# Flatten the bottom stones
|
|
m = (p[4, 1] - p[6, 1]) / (p[4, 0] - p[6, 0])
|
|
p[[4, 5], 1] = p[0, 1]
|
|
p[[4, 5], 0] = (p[4, 1] - p[6, 1]) / m + p[6, 0]
|
|
|
|
m = (p[-2, 1] - p[-4, 1]) / (p[-2, 0] - p[-4, 0])
|
|
p[[-2, -1], 1] = p[-5, 1]
|
|
p[[-2, -1], 0] = (p[-2, 1] - p[-4, 1]) / m + p[-4, 0]
|
|
|
|
# Shift entire arch so the base is at y=0.1
|
|
min_y = min(p[:, 1])
|
|
p[:, 1] += 0.1 - min_y
|
|
|
|
dir = (pathlib.Path(__file__).resolve().parents[2] / "meshes" / "arch" /
|
|
f"num_stones={nsegs:d}")
|
|
dir.mkdir(parents=True, exist_ok=True)
|
|
# print(f"Saving meshes to {dir}")
|
|
|
|
# Write to obj files, one for each segment...
|
|
for i in range(nsegs):
|
|
filename = dir / f"stone-{i+1:02d}.obj"
|
|
with open(filename, 'w') as f:
|
|
# vertices
|
|
for k in range(8):
|
|
r = i * 8 + k
|
|
f.write("v {:f} {:f} {:f}\n".format(*p[r]))
|
|
# faces
|
|
f.write("f 1 3 4\n") # -x
|
|
f.write("f 4 2 1\n") # -x
|
|
f.write("f 5 6 8\n") # +x
|
|
f.write("f 8 7 5\n") # +x
|
|
f.write("f 1 2 6\n") # -y
|
|
f.write("f 6 5 1\n") # -y
|
|
f.write("f 3 7 8\n") # +y
|
|
f.write("f 8 4 3\n") # +y
|
|
f.write("f 1 5 7\n") # -z
|
|
f.write("f 7 3 1\n") # -z
|
|
f.write("f 2 4 8\n") # +z
|
|
f.write("f 8 6 2\n") # +z
|
|
|
|
return p
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) > 1:
|
|
assert(int(sys.argv[1]) % 2)
|
|
large_arch(nsegs=int(sys.argv[1]))
|
|
else:
|
|
large_arch()
|
|
|