or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmaterials.mdmath-utilities.mdpost-processing.mdscene-data.mdscene-loading.md

math-utilities.mddocs/

0

# Matrix and Math Utilities

1

2

Mathematical operations for 3D transformations, matrix decomposition, and data type conversions between ASSIMP and Python formats. PyAssimp provides utilities for working with transformation matrices, vectors, and converting C data structures to Python-friendly formats.

3

4

## Capabilities

5

6

### Data Type Conversion

7

8

Convert ASSIMP C structures to Python tuples or NumPy arrays for mathematical operations.

9

10

```python { .api }

11

def make_tuple(ai_obj, type=None):

12

"""

13

Convert ASSIMP objects to Python tuples/numpy arrays.

14

15

Automatically detects object type and converts appropriately:

16

- Matrix4x4 → 4x4 array/nested list

17

- Matrix3x3 → 3x3 array/nested list

18

- Vector types → 1D array/list

19

- Other structures → list of field values

20

21

Parameters:

22

- ai_obj: ASSIMP structure object (Matrix4x4, Matrix3x3, Vector3D, etc.)

23

- type: Optional type hint (usually auto-detected)

24

25

Returns:

26

- numpy.ndarray if numpy is available, otherwise nested Python lists

27

- For matrices: 2D array with proper dimensions

28

- For vectors: 1D array with component values

29

"""

30

```

31

32

Usage examples:

33

34

```python

35

import pyassimp

36

import numpy as np

37

38

scene = pyassimp.load("model.dae")

39

40

# Convert node transformation matrices

41

node = scene.rootnode

42

if hasattr(node, 'transformation'):

43

# Transformation is already converted to array/list by PyAssimp

44

transform = node.transformation

45

46

if isinstance(transform, np.ndarray):

47

print(f"Transform matrix shape: {transform.shape}")

48

print(f"Matrix type: numpy array")

49

50

# Extract components

51

translation = transform[:3, 3]

52

rotation_part = transform[:3, :3]

53

54

print(f"Translation: {translation}")

55

print(f"Rotation matrix:\n{rotation_part}")

56

else:

57

print(f"Transform matrix: {len(transform)}x{len(transform[0]) if transform else 0}")

58

print(f"Matrix type: nested list")

59

60

# Manual conversion if needed

61

for mesh in scene.meshes:

62

if hasattr(mesh, 'vertices') and mesh.vertices:

63

# Vertices are already converted

64

vertices = mesh.vertices

65

print(f"Vertices shape: {vertices.shape if hasattr(vertices, 'shape') else len(vertices)}")

66

67

pyassimp.release(scene)

68

```

69

70

### Matrix Decomposition

71

72

Decompose 4x4 transformation matrices into translation, rotation, and scaling components.

73

74

```python { .api }

75

def decompose_matrix(matrix):

76

"""

77

Decompose 4x4 transformation matrix into components.

78

79

Uses ASSIMP's aiDecomposeMatrix function to accurately decompose

80

transformation matrices into their constituent parts.

81

82

Parameters:

83

- matrix: Matrix4x4 object (ASSIMP structure, not numpy array)

84

85

Returns:

86

Tuple of (scaling, rotation, position):

87

- scaling: Vector3D with scale factors for X, Y, Z axes

88

- rotation: Quaternion representing rotation

89

- position: Vector3D with translation values

90

91

Raises:

92

AssimpError: If matrix is not a Matrix4x4 structure

93

"""

94

```

95

96

Usage examples:

97

98

```python

99

import pyassimp

100

from pyassimp.structs import Matrix4x4

101

102

scene = pyassimp.load("animated_model.dae")

103

104

def analyze_transformation(node):

105

"""Analyze transformation matrix of a scene node."""

106

107

print(f"Node: {node.name}")

108

109

# PyAssimp automatically converts transformations to arrays/lists

110

# For decomposition, we need the original Matrix4x4 structure

111

# This is typically available through lower-level access

112

113

if hasattr(node, 'transformation'):

114

transform = node.transformation

115

116

# If we have a numpy array, we can manually extract components

117

if hasattr(transform, 'shape') and transform.shape == (4, 4):

118

# Extract translation (last column, first 3 rows)

119

translation = transform[:3, 3]

120

121

# Extract rotation/scale matrix (upper-left 3x3)

122

rotation_scale = transform[:3, :3]

123

124

# Extract scale (length of each column vector)

125

scale_x = np.linalg.norm(rotation_scale[:, 0])

126

scale_y = np.linalg.norm(rotation_scale[:, 1])

127

scale_z = np.linalg.norm(rotation_scale[:, 2])

128

129

print(f" Translation: ({translation[0]:.3f}, {translation[1]:.3f}, {translation[2]:.3f})")

130

print(f" Scale: ({scale_x:.3f}, {scale_y:.3f}, {scale_z:.3f})")

131

132

# Extract rotation matrix (normalized)

133

rotation_matrix = rotation_scale / [scale_x, scale_y, scale_z]

134

print(f" Rotation matrix:\n{rotation_matrix}")

135

136

# Recursively process children

137

for child in node.children:

138

analyze_transformation(child)

139

140

analyze_transformation(scene.rootnode)

141

pyassimp.release(scene)

142

```

143

144

### Vector and Matrix Types

145

146

Core mathematical data types used throughout PyAssimp.

147

148

```python { .api }

149

class Vector2D:

150

"""

151

2D vector with X and Y components.

152

153

Attributes:

154

- x: float, X coordinate

155

- y: float, Y coordinate

156

"""

157

x: float

158

y: float

159

160

class Vector3D:

161

"""

162

3D vector with X, Y, and Z components.

163

164

Attributes:

165

- x: float, X coordinate

166

- y: float, Y coordinate

167

- z: float, Z coordinate

168

"""

169

x: float

170

y: float

171

z: float

172

173

class Matrix3x3:

174

"""

175

3x3 transformation matrix.

176

177

Matrix elements stored in row-major order:

178

[a1 a2 a3]

179

[b1 b2 b3]

180

[c1 c2 c3]

181

182

Attributes:

183

- a1, a2, a3: float, first row elements

184

- b1, b2, b3: float, second row elements

185

- c1, c2, c3: float, third row elements

186

"""

187

a1: float; a2: float; a3: float

188

b1: float; b2: float; b3: float

189

c1: float; c2: float; c3: float

190

191

class Matrix4x4:

192

"""

193

4x4 transformation matrix for 3D transformations.

194

195

Stores translation, rotation, and scaling transformations

196

in homogeneous coordinates.

197

"""

198

# Matrix elements (implementation details in C structure)

199

200

class Quaternion:

201

"""

202

Quaternion for representing rotations.

203

204

Attributes:

205

- w: float, scalar component

206

- x: float, X component of vector part

207

- y: float, Y component of vector part

208

- z: float, Z component of vector part

209

"""

210

w: float

211

x: float

212

y: float

213

z: float

214

215

class Color4D:

216

"""

217

RGBA color representation.

218

219

Attributes:

220

- r: float, red component (0.0-1.0)

221

- g: float, green component (0.0-1.0)

222

- b: float, blue component (0.0-1.0)

223

- a: float, alpha component (0.0-1.0)

224

"""

225

r: float

226

g: float

227

b: float

228

a: float

229

```

230

231

Usage examples:

232

233

```python

234

import pyassimp

235

import math

236

237

def vector_operations_example():

238

"""Example of working with vector data."""

239

240

scene = pyassimp.load("model.obj")

241

242

for mesh in scene.meshes:

243

if mesh.vertices:

244

vertices = mesh.vertices

245

246

# Calculate mesh bounds

247

if hasattr(vertices, 'shape'): # NumPy array

248

min_bounds = np.min(vertices, axis=0)

249

max_bounds = np.max(vertices, axis=0)

250

center = (min_bounds + max_bounds) / 2

251

size = max_bounds - min_bounds

252

253

print(f"Mesh bounds: {min_bounds} to {max_bounds}")

254

print(f"Mesh center: {center}")

255

print(f"Mesh size: {size}")

256

257

else: # Python list

258

if vertices:

259

# Manual calculation for list format

260

min_x = min(v[0] for v in vertices)

261

max_x = max(v[0] for v in vertices)

262

min_y = min(v[1] for v in vertices)

263

max_y = max(v[1] for v in vertices)

264

min_z = min(v[2] for v in vertices)

265

max_z = max(v[2] for v in vertices)

266

267

center = [(min_x + max_x)/2, (min_y + max_y)/2, (min_z + max_z)/2]

268

size = [max_x - min_x, max_y - min_y, max_z - min_z]

269

270

print(f"Mesh center: {center}")

271

print(f"Mesh size: {size}")

272

273

pyassimp.release(scene)

274

275

vector_operations_example()

276

```

277

278

## Advanced Mathematical Operations

279

280

### Transformation Chains

281

282

```python

283

import pyassimp

284

import numpy as np

285

286

def compute_world_transform(node, parent_transform=None):

287

"""Compute world transformation for a node."""

288

289

if parent_transform is None:

290

parent_transform = np.eye(4) # Identity matrix

291

292

# Get node's local transformation

293

local_transform = node.transformation

294

295

# Ensure it's a numpy array

296

if not isinstance(local_transform, np.ndarray):

297

local_transform = np.array(local_transform)

298

299

# Compute world transformation

300

world_transform = parent_transform @ local_transform

301

302

return world_transform

303

304

def traverse_with_transforms(node, parent_transform=None):

305

"""Traverse scene graph computing world transforms."""

306

307

world_transform = compute_world_transform(node, parent_transform)

308

309

print(f"Node '{node.name}' world transform:")

310

print(world_transform)

311

312

# Process node's meshes with world transform

313

for mesh_ref in node.meshes:

314

mesh = mesh_ref if hasattr(mesh_ref, 'vertices') else scene.meshes[mesh_ref]

315

print(f" Mesh: {len(mesh.vertices) if mesh.vertices else 0} vertices")

316

317

# Recursively process children

318

for child in node.children:

319

traverse_with_transforms(child, world_transform)

320

321

# Usage

322

scene = pyassimp.load("hierarchical_model.dae")

323

traverse_with_transforms(scene.rootnode)

324

pyassimp.release(scene)

325

```

326

327

### Vertex Transformation

328

329

```python

330

import pyassimp

331

import numpy as np

332

333

def transform_vertices(vertices, transformation_matrix):

334

"""Transform vertex positions by a 4x4 matrix."""

335

336

if not isinstance(vertices, np.ndarray):

337

vertices = np.array(vertices)

338

339

# Add homogeneous coordinate (w=1) for position vectors

340

ones = np.ones((vertices.shape[0], 1))

341

homogeneous_vertices = np.hstack([vertices, ones])

342

343

# Apply transformation

344

transformed = (transformation_matrix @ homogeneous_vertices.T).T

345

346

# Return 3D coordinates (drop homogeneous coordinate)

347

return transformed[:, :3]

348

349

def transform_normals(normals, transformation_matrix):

350

"""Transform normal vectors (use inverse transpose for correct transformation)."""

351

352

if not isinstance(normals, np.ndarray):

353

normals = np.array(normals)

354

355

# Use inverse transpose of upper-left 3x3 for normals

356

rotation_part = transformation_matrix[:3, :3]

357

normal_transform = np.linalg.inv(rotation_part).T

358

359

# Transform normals

360

transformed_normals = (normal_transform @ normals.T).T

361

362

# Normalize

363

norms = np.linalg.norm(transformed_normals, axis=1, keepdims=True)

364

return transformed_normals / norms

365

366

# Usage example

367

scene = pyassimp.load("model.obj")

368

369

# Create a transformation (e.g., rotate 45 degrees around Y axis)

370

angle = np.radians(45)

371

rotation_y = np.array([

372

[np.cos(angle), 0, np.sin(angle), 0],

373

[0, 1, 0, 0],

374

[-np.sin(angle), 0, np.cos(angle), 0],

375

[0, 0, 0, 1]

376

])

377

378

for mesh in scene.meshes:

379

if mesh.vertices:

380

# Transform vertices

381

transformed_vertices = transform_vertices(mesh.vertices, rotation_y)

382

print(f"Transformed {len(transformed_vertices)} vertices")

383

384

# Transform normals if available

385

if mesh.normals:

386

transformed_normals = transform_normals(mesh.normals, rotation_y)

387

print(f"Transformed {len(transformed_normals)} normals")

388

389

pyassimp.release(scene)

390

```

391

392

### Geometric Calculations

393

394

```python

395

import pyassimp

396

import numpy as np

397

398

def calculate_mesh_properties(mesh):

399

"""Calculate various geometric properties of a mesh."""

400

401

if not mesh.vertices:

402

return {}

403

404

vertices = mesh.vertices

405

if not isinstance(vertices, np.ndarray):

406

vertices = np.array(vertices)

407

408

properties = {}

409

410

# Bounding box

411

properties['bbox_min'] = np.min(vertices, axis=0)

412

properties['bbox_max'] = np.max(vertices, axis=0)

413

properties['bbox_center'] = (properties['bbox_min'] + properties['bbox_max']) / 2

414

properties['bbox_size'] = properties['bbox_max'] - properties['bbox_min']

415

416

# Centroid

417

properties['centroid'] = np.mean(vertices, axis=0)

418

419

# Bounding sphere (approximate)

420

center = properties['centroid']

421

distances = np.linalg.norm(vertices - center, axis=1)

422

properties['bounding_sphere_radius'] = np.max(distances)

423

424

# Surface area (approximate, assuming triangulated mesh)

425

if mesh.faces:

426

total_area = 0

427

faces = mesh.faces

428

429

for face in faces:

430

indices = face.indices if hasattr(face, 'indices') else face

431

if len(indices) >= 3:

432

# Triangle area using cross product

433

v0, v1, v2 = vertices[indices[0]], vertices[indices[1]], vertices[indices[2]]

434

edge1 = v1 - v0

435

edge2 = v2 - v0

436

area = 0.5 * np.linalg.norm(np.cross(edge1, edge2))

437

total_area += area

438

439

properties['surface_area'] = total_area

440

441

# Volume (for closed meshes, using divergence theorem)

442

if mesh.faces:

443

volume = 0

444

faces = mesh.faces

445

446

for face in faces:

447

indices = face.indices if hasattr(face, 'indices') else face

448

if len(indices) >= 3:

449

v0, v1, v2 = vertices[indices[0]], vertices[indices[1]], vertices[indices[2]]

450

# Contribution to volume

451

volume += np.dot(v0, np.cross(v1, v2)) / 6.0

452

453

properties['volume'] = abs(volume)

454

455

return properties

456

457

# Usage

458

scene = pyassimp.load("model.stl")

459

460

for i, mesh in enumerate(scene.meshes):

461

props = calculate_mesh_properties(mesh)

462

463

print(f"Mesh {i} properties:")

464

for key, value in props.items():

465

if isinstance(value, np.ndarray):

466

print(f" {key}: {value}")

467

else:

468

print(f" {key}: {value:.6f}")

469

470

pyassimp.release(scene)

471

```

472

473

## Performance Considerations

474

475

1. **NumPy Integration**: Install NumPy for significantly better performance with mathematical operations

476

2. **Memory Usage**: Mathematical operations create temporary arrays; be mindful of memory usage with large meshes

477

3. **In-place Operations**: Use in-place NumPy operations when possible to reduce memory allocation

478

4. **Matrix Storage**: ASSIMP uses row-major order; NumPy defaults to row-major which is compatible

479

5. **Precision**: ASSIMP uses single-precision floats; consider precision requirements for your calculations

480

481

## Common Mathematical Patterns

482

483

```python

484

import pyassimp

485

import numpy as np

486

487

# Pattern 1: Mesh analysis pipeline

488

def analyze_mesh_geometry(filename):

489

scene = pyassimp.load(filename)

490

491

for mesh in scene.meshes:

492

if mesh.vertices:

493

vertices = np.array(mesh.vertices) if not isinstance(mesh.vertices, np.ndarray) else mesh.vertices

494

495

# Quick statistics

496

stats = {

497

'vertex_count': len(vertices),

498

'bounds': (np.min(vertices, axis=0), np.max(vertices, axis=0)),

499

'centroid': np.mean(vertices, axis=0)

500

}

501

502

yield stats

503

504

pyassimp.release(scene)

505

506

# Pattern 2: Transformation application

507

def apply_transform_to_scene(scene, transform_matrix):

508

"""Apply transformation to all meshes in scene."""

509

510

for mesh in scene.meshes:

511

if mesh.vertices:

512

mesh.vertices = transform_vertices(mesh.vertices, transform_matrix)

513

if mesh.normals:

514

mesh.normals = transform_normals(mesh.normals, transform_matrix)

515

516

# Pattern 3: Data format conversion

517

def convert_to_numpy(mesh):

518

"""Ensure mesh data is in NumPy format."""

519

520

if mesh.vertices and not isinstance(mesh.vertices, np.ndarray):

521

mesh.vertices = np.array(mesh.vertices, dtype=np.float32)

522

523

if mesh.normals and not isinstance(mesh.normals, np.ndarray):

524

mesh.normals = np.array(mesh.normals, dtype=np.float32)

525

526

if mesh.faces:

527

# Convert faces to consistent format

528

face_indices = []

529

for face in mesh.faces:

530

indices = face.indices if hasattr(face, 'indices') else face

531

face_indices.append(indices)

532

mesh.faces = face_indices

533

```