Skip to content

Commit

Permalink
Ensure equal edge lengths within edge loops
Browse files Browse the repository at this point in the history
  • Loading branch information
Zyl9393 committed Jun 5, 2024
1 parent 582ad6b commit 743cb29
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 84 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 1.1 (2024-06-05)
* Rewrote math to guarantee equal edge length within all edge loops instead of just the edges of the three axis-aligned circles within the sphere.

# 1.0 (2024-04-29)
* Initial release.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Even Quad Sphere

Special case of quad sphere which maximizes equality of edge lengths.
Special case of quad sphere which makes lengths of edges within same edge loops equal.

Quad sphere from "Extra Objects" addon (unequal edge lengths even on axis-aligned circles):

![Addon preferences](img/quad_sphere_extra_objects_addon.png "Extra Objects addon quad sphere")

Quad sphere from this addon (equal edge lengths on axis-aligned circles):
Quad sphere from this addon (equal edge lengths of edges within same edge loops):

![Addon preferences](img/even_quad_sphere.png "Quad sphere made by this addon")

Expand Down
2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Even Quad Sphere",
"description": "Special case of quad sphere which maximizes equality of edge lengths",
"author": "Zyl",
"version": (1, 0),
"version": (1, 1),
"blender": (2, 93, 0),
"category": "Add Mesh"
}
Expand Down
168 changes: 87 additions & 81 deletions add_mesh_even_quad_sphere.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import bpy
from bpy_extras import object_utils
from itertools import permutations
from math import (
copysign, pi,
sqrt, tan,
pi, sqrt,
)
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
StringProperty,
)
from mathutils import (
Vector,
Quaternion,
)


def even_quad_sphere(slices, size):
Expand All @@ -26,77 +25,77 @@ def even_quad_sphere(slices, size):
vertIndex = 0

# Top
for z in range(0, slices + 1):
for x in range(0, slices + 1):
i = x + z * lines
for v in range(0, lines):
for u in range(0, lines):
i = u + v * lines
sideVerts[0][i] = vertIndex
vertIndex = vertIndex + 1
verts.append([0.0, 0.0, 0.0])

# Front
for y in range(0, slices + 1):
for x in range(0, slices + 1):
i = x + y * lines
if y == slices:
sideVerts[1][i] = sideVerts[0][x]
for v in range(0, lines):
for u in range(0, lines):
i = u + v * lines
if v == lines - 1:
sideVerts[1][i] = sideVerts[0][u]
else:
sideVerts[1][i] = vertIndex
vertIndex = vertIndex + 1
verts.append([0.0, 0.0, 0.0])

# Right
for y in range(0, slices + 1):
for z in range(0, slices + 1):
i = z + y * lines
if z == 0:
sideVerts[2][i] = sideVerts[1][i + slices]
elif y == slices:
sideVerts[2][i] = sideVerts[0][slices + z * lines]
for v in range(0, lines):
for u in range(0, lines):
i = u + v * lines
if u == 0:
sideVerts[2][i] = sideVerts[1][v * lines + lines - 1]
elif v == lines - 1:
sideVerts[2][i] = sideVerts[0][u * lines + lines - 1]
else:
sideVerts[2][i] = vertIndex
vertIndex = vertIndex + 1
verts.append([0.0, 0.0, 0.0])

# Back
for y in range(0, slices + 1):
for x in range(0, slices + 1):
i = x + y * lines
if x == slices:
sideVerts [3][i] = sideVerts[2][i]
elif y == slices:
sideVerts [3][i] = sideVerts[0][i]
for v in range(0, lines):
for u in range(0, lines):
i = u + v * lines
if u == 0:
sideVerts [3][i] = sideVerts[2][i + lines - 1]
elif v == lines - 1:
sideVerts [3][i] = sideVerts[0][lines * lines - 1 - u]
else:
sideVerts [3][i] = vertIndex
vertIndex = vertIndex + 1
verts.append([0.0, 0.0, 0.0])

# Left
for y in range(0, slices + 1):
for z in range(0, slices + 1):
i = z + y * lines
if z == 0:
sideVerts [4][i] = sideVerts [1][i]
elif y == slices:
sideVerts [4][i] = sideVerts [0][z * lines]
elif z == slices:
sideVerts [4][i] = sideVerts [3][i - slices]
for v in range(0, lines):
for u in range(0, lines):
i = u + v * lines
if u == 0:
sideVerts [4][i] = sideVerts [3][i + lines - 1]
elif v == lines - 1:
sideVerts [4][i] = sideVerts [0][(lines - 1 - u) * lines]
elif u == lines - 1:
sideVerts [4][i] = sideVerts [1][i - lines + 1]
else:
sideVerts [4][i] = vertIndex
vertIndex = vertIndex + 1
verts.append([0.0, 0.0, 0.0])

# Bottom
for z in range(0, slices + 1):
for x in range(0, slices + 1):
i = x + z * lines
if z == 0:
sideVerts [5][i] = sideVerts [1][x]
elif z == slices:
sideVerts [5][i] = sideVerts [3][x]
elif x == slices:
sideVerts [5][i] = sideVerts [2][z]
elif x == 0:
sideVerts [5][i] = sideVerts [4][z]
for v in range(0, lines):
for u in range(0, lines):
i = u + v * lines
if v == 0:
sideVerts [5][i] = sideVerts [3][lines - 1 - u]
elif v == lines - 1:
sideVerts [5][i] = sideVerts [1][u]
elif u == 0:
sideVerts [5][i] = sideVerts [4][v]
elif u == lines - 1:
sideVerts [5][i] = sideVerts [2][lines - 1 - v]
else:
sideVerts [5][i] = vertIndex
vertIndex = vertIndex + 1
Expand All @@ -105,46 +104,53 @@ def even_quad_sphere(slices, size):
faces = []
tempVerts = []
for s in range(0, 6):
invertedWinding = (s == 3) or (s == 4) or (s == 5)
for y in range(0, slices):
for x in range(0, slices):
i = x + y * lines
tempVerts.clear()
if invertedWinding:
tempVerts.append(sideVerts[s][i])
tempVerts.append(sideVerts[s][i + 1])
tempVerts.append(sideVerts[s][i + 1 + lines])
tempVerts.append(sideVerts[s][i + lines])
else:
tempVerts.append(sideVerts[s][i])
tempVerts.append(sideVerts[s][i + lines])
tempVerts.append(sideVerts[s][i + lines + 1])
tempVerts.append(sideVerts[s][i + 1])
tempVerts.append(sideVerts[s][i])
tempVerts.append(sideVerts[s][i + 1])
tempVerts.append(sideVerts[s][i + 1 + lines])
tempVerts.append(sideVerts[s][i + lines])
faces.append(tuple(tempVerts))

sides = ((0, 1, 0), (0, 0, -1), (1, 0, 0), (0, 0, 1), (-1, 0, 0), (0, -1, 0))
xDirections = ((1, 0, 0), (1, 0, 0), (0, 0, 1), (1, 0, 0), (0, 0, 1), (1, 0, 0))
yDirections = ((0, 0, 1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 0, 1))
quarterPi = pi / 4
for s in range(0, 6):
side = sides[s]
xDir = xDirections[s]
yDir = yDirections[s]
for y in range(0, slices + 1):
for x in range(0, slices + 1):
i = x + y * lines
# Top, Front, Right, Back, Left, Bottom
sides = ((0, 0, 1), (0, -1, 0), (1, 0, 0), (0, 1, 0), (-1, 0, 0), (0, 0, -1))
uDirections = ((1, 0, 0), (1, 0, 0), (0, 1, 0), (-1, 0, 0), (0, -1, 0), (1, 0, 0))
# vDirections = ((0, 1, 0), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, -1, 0)) # not used

# Range -1 to 1.
xLinear = (2 * x - slices) / slices
yLinear = (2 * y - slices) / slices

# More even distribution
xLinear = tan(xLinear * quarterPi)
yLinear = tan(yLinear * quarterPi)

newPos = [side[i] + xLinear * xDir[i] + yLinear * yDir[i] for i in range(0, 3)]
newPosLen = (newPos[0]*newPos[0] + newPos[1]*newPos[1] + newPos[2]*newPos[2]) ** (1/2)
newPos = [newPos[i] / newPosLen for i in range(0, 3)]
cornerArcAngle = Vector((0, 1, 1)).angle(Vector((1, 1, 1)))
for s in range(0, 6):
side = Vector(sides[s])
uDir = Vector(uDirections[s])
# vDir = Vector(vDirections[s])
for v in range(0, lines):
vLinear = (2 * v - slices) / slices

p0 = (side - uDir).normalized()
p1 = (side + uDir).normalized()
pCenter = Vector(side)

p0.rotate(Quaternion(side + uDir, -cornerArcAngle * vLinear))
p1.rotate(Quaternion(side - uDir, cornerArcAngle * vLinear))
pCenter.rotate(Quaternion(uDir, -pi / 4 * vLinear))

zDelta = pCenter - 0.5 * (p0 + p1)
slantAngle = side.angle(zDelta)
slantAngle = -slantAngle if vLinear < 0 else slantAngle
span = Vector(side)
span.rotate(Quaternion(uDir, -slantAngle))
rotAxis = span.cross(uDir).normalized()

ringSegmentDist = pCenter.dot(rotAxis)
ringSegmentRadius = sqrt(1.0 - ringSegmentDist * ringSegmentDist) # equal to sin(acos(ringSegmentDist))
arcAngle = 2 * zDelta.angle(p0 - (pCenter - ringSegmentRadius * zDelta.normalized()))

for u in range(0, lines):
newPos = Vector(p0)
rotation = Quaternion(rotAxis, arcAngle * (u / slices))
newPos.rotate(rotation)
i = u + v * lines
verts[sideVerts[s][i]][0] = newPos[0]
verts[sideVerts[s][i]][1] = newPos[1]
verts[sideVerts[s][i]][2] = newPos[2]
Expand Down
Binary file modified img/even_quad_sphere.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 743cb29

Please sign in to comment.