Example lib_zb_math¶
Contains a basic math library used by some of the modeling examples.
Code¶
"""Contains a basic math library used by some of the modeling examples.
This file does not contain any ZBrush specific functionality. THIS IS STILL A CODE EXAMPLE AND NOT
A FEATURE. We cannot debug or extend this library. But it can be starting point for you to fill some
gaps in ZBrush's current API.
Functions:
interpolate: Linearly interpolates between two values.
Classes:
Vector: Represents a three-dimensional floating point vector.
ValueNoise: Realizes a naive value noise.
"""
__author__ = "Ferdinand Hoppe"
__date__ = "22/08/2025"
__copyright__ = "Maxon Computer"
import math
import typing
def interpolate(a: float, b: float, t: float) -> float:
"""Linearly interpolates between #a and #b using #t.
"""
return a + t * (b - a)
class Vector:
"""Represents a three-dimensional floating point vector.
Implements basic vector operations and supports iteration over its components to allow unpacking.
I.e., you can do the following:
v: Vector = Vector(1.0, 2.0, 3.0)
def foo(x: float, y: float, z: float) -> None:
...
foo(*v)
"""
def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> None:
"""Constructs a vector.
"""
self.x = x; self.y = y; self.z = z
def __add__(self, other: "Vector") -> "Vector":
"""Adds #other to #self and returns the result.
"""
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
def __iadd__(self, other: "Vector") -> "Vector":
"""Adds #other to #self.
"""
self.x += other.x; self.y += other.y; self.z += other.z
return self
def __sub__(self, other: "Vector") -> "Vector":
"""Subtracts #other from #self and returns the result.
"""
return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
def __isub__(self, other: "Vector") -> "Vector":
"""Subtracts #other from #self.
"""
self.x -= other.x; self.y -= other.y; self.z -= other.z
return self
def __mul__(self, other: float) -> "Vector":
"""Multiplies #self by the scalar #other and returns the result.
"""
return Vector(self.x * other, self.y * other, self.z * other)
def __rmul__(self, other: float) -> "Vector":
"""Multiplies a scalar by #self and returns the result.
"""
return self * other
def __imul__(self, other: float) -> "Vector":
"""Multiplies #self by the scalar #other.
"""
self.x *= other; self.y *= other; self.z *= other
return self
def __iter__(self) -> typing.Iterator[float]:
"""Iterates over the components of the vector.
"""
yield self.x
yield self.y
yield self.z
def __repr__(self) -> str:
"""Returns a string representation of the vector.
"""
return (f"{self.__class__.__name__}({round(self.x, 3)}, {round(self.y, 3)}, "
f"{round(self.z, 3)})")
def dot(self, other: "Vector") -> float:
"""Returns the dot product of #self and #other.
"""
return self.x * other.x + self.y * other.y + self.z * other.z
def cross(self, other: "Vector") -> "Vector":
"""Returns the cross product of #self and #other.
"""
return Vector(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x
)
def length(self) -> float:
"""Returns the length of #self.
"""
return math.sqrt(self.length_squared())
def length_squared(self) -> float:
"""Returns the squared length of #self.
"""
return self.x ** 2 + self.y ** 2 + self.z ** 2
def normalized(self) -> "Vector":
"""Returns the unit vector of #self.
"""
length = self.length()
if length > 0:
return Vector(self.x / length, self.y / length, self.z / length)
return Vector()
def rotate(self, angles: "Vector") -> "Vector":
"""Rotates #self by the given Euler #angles in radians.
"""
x, y, z = self.x, self.y, self.z
cx, cy, cz = map(math.cos, (angles.x, angles.y, angles.z))
sx, sy, sz = map(math.sin, (angles.x, angles.y, angles.z))
return Vector(
x * cy * cz - y * cy * sz + z * sy,
x * (sx * sy * cz + cx * sz) + y * (cx * sy * sz - sx * cz) - z * sx * cy,
x * (-cx * sy * cz + sx * sz) + y * (sx * sy * sz + cx * cz) + z * cx * cy
)
class ValueNoise:
"""Realizes a naive value noise generator.
This was written for the lightning curves example and is not by any means a production quality
noise implementation. It is just meant to demonstrate how you could implement a simple value
noise in Python using the Vector class defined above.
"""
SEED: float = 43758.5453 # The seed used by the hash function.
@staticmethod
def hash_31(p: Vector) -> float:
"""Returns a pseudo random hash value for the given vector #p.
"""
return ((math.sin(p.x * 12.9898 + p.y * 78.233 + p.z * 37.719) * ValueNoise.SEED) % 1.0)
@staticmethod
def noise_31(p: Vector, scale: float = 1.0) -> float:
"""Returns a noise float value for the given vector #p.
"""
# Get the integer cell coordinate and the local coordinate within the cell for the vector #p.
cell: Vector = Vector(math.floor(p.x), math.floor(p.y), math.floor(p.z))
local: Vector = Vector(p.x - cell.x, p.y - cell.y, p.z - cell.z)
# Compute the pseudo random hashes for the eight corners of the cell.
v000: float = ValueNoise.hash_31(Vector(cell.x, cell.y, cell.z))
v001: float = ValueNoise.hash_31(Vector(cell.x, cell.y, cell.z+1))
v010: float = ValueNoise.hash_31(Vector(cell.x, cell.y+1, cell.z))
v011: float = ValueNoise.hash_31(Vector(cell.x, cell.y+1, cell.z+1))
v100: float = ValueNoise.hash_31(Vector(cell.x+1, cell.y, cell.z))
v101: float = ValueNoise.hash_31(Vector(cell.x+1, cell.y, cell.z+1))
v110: float = ValueNoise.hash_31(Vector(cell.x+1, cell.y+1, cell.z))
v111: float = ValueNoise.hash_31(Vector(cell.x+1, cell.y+1, cell.z+1))
# Compute the result by bilinearly interpolating the the cell corners. Here is a lot of
# room for optimization, e.g., by using a smoother interpolation function, as linear
# interpolation produces visible artifacts.
x00: float = interpolate(v000, v100, local.x)
x01: float = interpolate(v001, v101, local.x)
x10: float = interpolate(v010, v110, local.x)
x11: float = interpolate(v011, v111, local.x)
y0: float = interpolate(x00, x10, local.y)
y1: float = interpolate(x01, x11, local.y)
return interpolate(y0, y1, local.z) * scale
@staticmethod
def noise_13(p: float, scale: float = 1.0) -> Vector:
"""Returns a noise vector for the given float value #p.
"""
return Vector(
ValueNoise.noise_31(Vector(p, 0, 0), scale),
ValueNoise.noise_31(Vector(0, p, 0), scale),
ValueNoise.noise_31(Vector(0, 0, p), scale)
)
@staticmethod
def turbulence_13(p: float, scale: float = 1.0, octaves: int = 4, frequency: float = 1.0,
amplitude: float = 1.0) -> Vector:
"""Returns a turbulence vector for the given float value #p.
"""
res: Vector = Vector()
for _ in range(octaves):
res += ValueNoise.noise_13(p * frequency, scale) * amplitude
frequency *= 2.0
amplitude *= 0.5
return res