# ============================================================================
# CC0 "No Rights Reserved"
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. See http://creativecommons.org/publicdomain/zero/1.0/.
# ============================================================================

# Warps a reference plane to a surface and keeps off-planar nodes perpendicular to this surface.
# Select nodes before using.


import math

# Lr, Ls:   dimensions of reference plane
# R:        radius of spiral
# w:        number of windings

Lr = 30.0
Ls = 1.0
R = 10.0
w = 1.0


# fx calculates the x-coordinate of the surface
def fx(r, s, Lr, Ls, R, w):
    return (R - s) * math.cos(w * (r / Lr) * 2.0 * math.pi)


# fx calculates the y-coordinate of the surface
def fy(r, s, Lr, Ls, R, w):
    return (R - s) * math.sin(w * (r / Lr) * 2.0 * math.pi)


# fx calculates the z-coordinate of the surface
def fz(r, s, Lr, Ls, R, w):
    return r


# dfxdr calculates the r-derivative of the x-coordinate of the surface at point (r, s)
def dfxdr(r, s, Lr, Ls, R, w):
    return - (R - s) * math.sin(w * (r / Lr) * 2.0 * math.pi) * w * 2.0 * math.pi / Lr


# dfxds calculates the s-derivative of the x-coordinate of the surface at point (r, s)
def dfxds(r, s, Lr, Ls, R, w):
    return - math.cos(w * (r / Lr) * 2.0 * math.pi)


# dfydr calculates the r-derivative of the y-coordinate of the surface at point (r, s)
def dfydr(r, s, Lr, Ls, R, w):
    return (R - s) * math.cos(w * (r / Lr) * 2.0 * math.pi) * w * 2.0 * math.pi / Lr


# dfyds calculates the s-derivative of the y-coordinate of the surface at point (r, s)
def dfyds(r, s, Lr, Ls, R, w):
    return - math.sin(w * (r / Lr) * 2.0 * math.pi)


# dfzdr calculates the r-derivative of the z-coordinate of the surface at point (r, s)
def dfzdr(r, s, Lr, Ls, R, w):
    return 1.0


# dfzds calculates the s-derivative of the z-coordinate of the surface at point (r, s)
def dfzds(r, s, Lr, Ls, R, w):
    return 0.0


for node_id in mw.selected_nodes():
    coord = mw.node(node_id)
    r = coord.x
    s = coord.y
    t = coord.z

    # v_mid = position vector of surface
    v_mid = [fx(r, s, Lr, Ls, R, w), fy(r, s, Lr, Ls, R, w), fz(r, s, Lr, Ls, R, w)]

    # dvdr = tangent vector to surface = d(v_mid)/dr
    # dvds = tangent vector to surface = d(v_mid)/ds
    dvdr = [dfxdr(r, s, Lr, Ls, R, w), dfydr(r, s, Lr, Ls, R, w), dfzdr(r, s, Lr, Ls, R, w)]
    dvds = [dfxds(r, s, Lr, Ls, R, w), dfyds(r, s, Lr, Ls, R, w), dfzds(r, s, Lr, Ls, R, w)]

    # n = vector normal to surface at (r, s) = dvdr x dvds
    n = [dvdr[1] * dvds[2] - dvdr[2] * dvds[1],
         dvdr[2] * dvds[0] - dvdr[0] * dvds[2],
         dvdr[0] * dvds[1] - dvdr[1] * dvds[0]]

    # norm = length of normal vector
    norm = math.sqrt(n[0] ** 2 + n[1] ** 2 + n[2] ** 2)

    # nn = unit normal vector at (r, s)
    nn = [entry / norm for entry in n]

    mw.set_node_x(node_id, v_mid[0] + t * nn[0])
    mw.set_node_y(node_id, v_mid[1] + t * nn[1])
    mw.set_node_z(node_id, v_mid[2] + t * nn[2])
