Skip to article frontmatterSkip to article content

Chapter 9

9.1 Understanding Points in 3D

Setting working directory

import os
new_directory = <'/your/path/to/Ch08'>
os.chdir(new_directory)

Create a new stage and define a Points Prim

from pxr import Usd, UsdGeom, Gf, Sdf
stage = Usd.Stage.CreateNew("points_example.usda")    

UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)    

points = UsdGeom.Points.Define(stage, "/Points")    

stage.SetDefaultPrim(points.GetPrim())    

Populate position data for points

positions = [
    Gf.Vec3f(1.0, 1.0, 1.0), 
    Gf.Vec3f(-1.0, -1.0, -1.0), 
    Gf.Vec3f(2.0, 0.5, 0.0)
]    

points.GetPointsAttr().Set(positions)    

stage.Save()

Add width attribute to each point

sizes = [0.5, 0.4, 0.2]    
points.GetWidthsAttr().Set(sizes)    

Add the same color attribute to all points

points_prim = points.GetPrim()    

uniform_color = [Gf.Vec3f(0.0, 1.0, 0.0)]    
points_prim.GetAttribute("primvars:displayColor").Set(uniform_color)    

stage.Save()

Assign different colors to each point

colors = [
	Gf.Vec3f(1.0, 0.0, 0.0),    
	Gf.Vec3f(0.0, 1.0, 0.0),    
	Gf.Vec3f(0.0, 0.0, 1.0)    
]

primvar = UsdGeom.PrimvarsAPI(points_prim).CreatePrimvar(
	"displayColor", Sdf.ValueTypeNames.Color3fArray, UsdGeom.Tokens.varying
)    
primvar.Set(colors)    

stage.Save()

Add dynamic color variations to points

z_coordinates = [pos[2] for pos in positions]    

z_min = min(z_coordinates)
z_max = max(z_coordinates)    

def interpolate_color(z):    
	normalized_z = (z - z_min) / (z_max - z_min) if z_max != z_min else 0    
	red = normalized_z    
	blue = 1.0 - normalized_z   
	return Gf.Vec3f(red, 0.0, blue)    

dynamic_colors = [interpolate_color(z) for z in z_coordinates]    

primvar = UsdGeom.PrimvarsAPI(points_prim).CreatePrimvar(
	"displayColor", Sdf.ValueTypeNames.Color3fArray, UsdGeom.Tokens.varying
)    

primvar.Set(dynamic_colors)    

stage.Save()

9.2 Converting Point Cloud Datasets to USD

Install ‘open3d’ package from a terminal within your python virtual environment

pip install open3d

Verify ‘open3d’ is installed correctly in terminal

python -c "import open3d; print(open3d.__version__)"

Start the Python interpreter in the terminal

python

Install ‘open3d’ for Google Collab users in Jupyter notebook

!pip install open3d    

import open3d    
print(open3d.__version__)    

Import modules, load the point cloud, and get the point data into a list of Gf.Vec3f values

import open3d as o3d  
from pxr import Usd, UsdGeom, Gf, Sdf

pcd = o3d.io.read_point_cloud("./Assets/bun_zipper_res2.ply")    

points = [Gf.Vec3f(p[0], p[1], p[2]) for p in pcd.points]    

Extract color information and create a list

if pcd.has_colors():
	colors = [Gf.Vec3f(c[0], c[1], c[2]) for c in pcd.colors]    
else:
	colors = [Gf.Vec3f(1.0, 1.0, 1.0)] * len(points)  

Create a stage to hold point cloud data

stage = Usd.Stage.CreateNew("point_cloud_conversion.usda")   

xform = UsdGeom.Xform.Define(stage, "/ScaledPointCloud")    

points_prim = UsdGeom.Points.Define(stage, "/ScaledPointCloud/Bunny")    

Set point positions and color

points_prim.GetPointsAttr().Set(points)    
points_prim.GetPrim().GetAttribute("primvars:displayColor").Set(colors)    

width = 0.01
points_prim.GetWidthsAttr().Set([width] * len(points))    

xform_xform = UsdGeom.XformCommonAPI(xform)
xform_xform.SetScale((100.0, 100.0, 100.0))   

stage.Save()

Increase the width of the points

width = 0.005    
points_prim.GetWidthsAttr().Set([width] * len(points))    

stage.Save()

9.3 Utilizing PointInstancer

Create a new stage, add a distant light, and define a point instancer

from pxr import Usd, UsdGeom, Gf, UsdLux
stage = Usd.Stage.CreateNew("forest.usda")
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)

world = UsdGeom.Xform.Define(stage, "/World")    
stage.SetDefaultPrim(world.GetPrim())    

distant_light = UsdLux.DistantLight.Define(stage, "/World/DistantLight")    
distant_light.AddRotateXYZOp().Set(Gf.Vec3d(60, -25, -35))    
distant_light.CreateIntensityAttr(5000.0)

point_instancer = UsdGeom.PointInstancer.Define(stage, "/World/Instancer")    

Import two tree assets and assig xforms using for loop and a path list

prototypes = UsdGeom.Scope.Define(stage, "/World/Prototypes")    

pine_paths = {
    "Pine1": './Assets/Pine1.usd',
    "Pine2": './Assets/Pine2.usd'
}    

for name, path in pine_paths.items():    
    xform = UsdGeom.Xform.Define(stage, f"/World/Prototypes/{name}")    
    xform.GetPrim().GetReferences().AddReference(path)    

prototypes.GetPrim().GetAttribute("visibility").Set("invisible")    

stage.Save()

Generate random positions for trees and use the Point Instancer to create instances of the objects.

import random

positions = [Gf.Vec3f(random.randint(0, 1000),    
                      random.randint(0, 1000), 0) for i in range(10 * 2)]   
point_instancer.GetPositionsAttr().Set(positions)   

Create an index list for instance and connect to PointInstancer

prototype_names = list(pine_paths.keys())    
prototype_paths = [f"/World/Prototypes/{name}" for name in prototype_names]    

num_instances_per_prototype = 10    
indices = [i for i in range(len(prototype_names)) for _ in range(num_instances_per_prototype)]    
point_instancer.GetProtoIndicesAttr().Set(indices)    

point_instancer.CreatePrototypesRel().SetTargets(prototype_paths)    

stage.Save()

Add variations to tree sizes

from pxr import Vt
import math

point_instancer = UsdGeom.PointInstancer(stage.GetPrimAtPath("/World/Instancer"))   

num_instances = len(point_instancer.GetProtoIndicesAttr().Get())    

scales = [Gf.Vec3f(s, s, s)
      	for s in [random.uniform(0.75, 1.25) for _ in range(num_instances)]]    
scales_array = Vt.Vec3fArray(scales)    

point_instancer.GetScalesAttr().Set(scales_array)    

stage.Save()

9.4 Understanding Curves in 3D

Define the number of frames in the animation in a dynamic way

from pxr import Usd, UsdGeom, Gf
import math

stage = Usd.Stage.CreateNew("curves_example.usda")

fps = 24.0    
duration_in_seconds = 5    
num_samples = int(fps * duration_in_seconds)    
stage.SetTimeCodesPerSecond(fps)    
stage.SetStartTimeCode(0)
stage.SetEndTimeCode(num_samples - 1)   

world = UsdGeom.Xform.Define(stage, "/World")
stage.SetDefaultPrim(world.GetPrim())   

Set up the curve

basis_curves = UsdGeom.BasisCurves.Define(stage, "/World/CurvesExample")    
control_points = [    
	Gf.Vec3f(0, 100, 700),
	Gf.Vec3f(250, 130, 550),
	Gf.Vec3f(500, 160, 525),
	Gf.Vec3f(700, 190, 575),
	Gf.Vec3f(900, 150, 700)
]
basis_curves.GetPointsAttr().Set(control_points)    
basis_curves.GetCurveVertexCountsAttr().Set([len(control_points)])    
basis_curves.GetTypeAttr().Set("cubic")    
basis_curves.GetBasisAttr().Set(UsdGeom.Tokens.bezier)    

stage.Save()

Create a camera, and apply a -90° rotation around the x axis

forest = UsdGeom.Xform.Define(stage, '/World/Forest')    
forest_refs = forest.GetPrim().GetReferences()    

forest_refs.AddReference("./forest.usda")    
forest_rotate_op = forest.AddRotateXOp()    
forest_rotate_op.Set(-90)    

camera = UsdGeom.Camera.Define(stage, "/World/Camera")    
cam_xform = UsdGeom.Xformable(camera)    
camera.GetFocalLengthAttr().Set(20)    

stage.Save()

Get Bézier curve points

def lerp(a, b, t):
	return a * (1 - t) + b * t   

# Optimized iterative De Casteljau algorithm for Bézier interpolation
def interpolate_bezier(points, t):    
    pts = points[:]  
    n = len(pts)    
    for r in range(1, n):    
        for i in range(n - r):    
            pts[i] = Gf.Vec3f(
                lerp(pts[i][0], pts[i+1][0], t),    
                lerp(pts[i][1], pts[i+1][1], t),    
                lerp(pts[i][2], pts[i+1][2], t)    
            )
    return pts[0] 

def interpolate_positions(points, num_samples):    
	return [interpolate_bezier(points, t / (num_samples - 1)) for t in range(num_samples)]

sampled_positions = interpolate_positions(control_points, num_samples)    

Set up the camera’s transformation operations

translate_op = None    
rotate_op_cam = None    
for op in cam_xform.GetOrderedXformOps():    
    if op.GetOpType() == UsdGeom.XformOp.TypeTranslate:
        translate_op = op    
    elif op.GetOpType() == UsdGeom.XformOp.TypeRotateXYZ:
        rotate_op_cam = op    

if translate_op is None:    
    translate_op = cam_xform.AddTranslateOp()

if rotate_op_cam is None:    
    rotate_op_cam = cam_xform.AddRotateXYZOp()

Animate the camera’s movement along the curve

look_at_target = Gf.Vec3f(500, 300, -500)    

def compute_euler_angles(eye, target):    
	direction = (target - eye).GetNormalized()    
	yaw = math.degrees(math.atan2(direction[0], direction[2])) + 180  
	pitch = math.degrees(math.asin(direction[1]))  
	roll = 0.0  
	return (pitch, yaw, roll)

# Animate the camera along the curve
for frame in range(num_samples):    
	pos = sampled_positions[frame]    
	translate_op.Set(value=pos, time=frame)    
	euler = compute_euler_angles(pos, look_at_target)    
	rotate_op_cam.Set(value=Gf.Vec3f(euler[0], euler[1], euler[2]), time=frame)   

stage.Save()