Cách Duyệt Đồ Thị Cảnh 3D trong TypeScript

Cách Duyệt Đồ Thị Cảnh 3D trong TypeScript

Đồ thị cảnh trong Aspose.3D FOSS cho TypeScript là một cây của Node các đối tượng có gốc tại scene.rootNode. Việc duyệt là đệ quy: mỗi nút cung cấp một childNodes iterable và một tùy chọn entity property. Hướng dẫn này chỉ ra cách duyệt toàn bộ cây, xác định các loại thực thể, và thu thập thống kê mesh.

Yêu cầu trước

  • Node.js 18 trở lên
  • TypeScript 5.0 trở lên
  • @aspose/3d đã cài đặt

Hướng Dẫn Từng Bước

Bước 1: Cài đặt và Nhập khẩu

Cài đặt gói:

npm install @aspose/3d

Nhập các lớp được sử dụng trong hướng dẫn này:

import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';

SceneMesh là các lớp cốt lõi. ObjLoadOptions được sử dụng trong ví dụ tải; thay thế bằng lớp tùy chọn phù hợp cho các định dạng khác.


Bước 2: Tải một Cảnh từ Tệp

Tạo một Scene và gọi scene.open() với một đường dẫn tệp. Phát hiện định dạng được thực hiện tự động từ các số ma thuật nhị phân, vì vậy bạn không cần chỉ định định dạng cho các tệp GLB, STL, hoặc 3MF:

import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';

const scene = new Scene();
scene.open('model.obj', new ObjLoadOptions());

console.log(`Root node: "${scene.rootNode.name}"`);
console.log(`Top-level children: ${scene.rootNode.childNodes.length}`);

Bạn cũng có thể tải từ một Buffer trong bộ nhớ bằng cách sử dụng scene.openFromBuffer(buffer, options); hữu ích trong các pipeline serverless nơi I/O đĩa không khả dụng.


Bước 3: Viết một Hàm Duyệt Đệ quy

Đệ quy trên childNodes là mẫu chuẩn. Hàm sẽ duyệt mỗi nút theo chiều sâu:

function traverse(node: any, depth = 0): void {
    const indent = '  '.repeat(depth);
    const entityType = node.entity ? node.entity.constructor.name : '-';
    console.log(`${indent}[${entityType}] ${node.name}`);
    for (const child of node.childNodes) {
        traverse(child, depth + 1);
    }
}

traverse(scene.rootNode);

Đối với một cảnh có một mesh tên Cube, đầu ra sẽ trông như sau:

[-] RootNode
  [Mesh] Cube

<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”

<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>

node.entitynull đối với các nút nhóm, xương và bộ định vị. The constructor.name kiểm tra hoạt động cho bất kỳ loại thực thể nào: Mesh, Camera, Light, v.v.


Bước 4: Truy cập Loại Thực Thể trên Mỗi Nút

Để thực hiện hành động dựa trên loại thực thể, sử dụng một instanceof kiểm tra sau khi có guard null:

import { Mesh } from '@aspose/3d/entities';

function visitWithTypeCheck(node: any, depth = 0): void {
    const indent = '  '.repeat(depth);
    if (node.entity instanceof Mesh) {
        const mesh = node.entity as Mesh;
        console.log(`${indent}MESH "${node.name}": ${mesh.controlPoints.length} vertices`);
    } else if (node.entity) {
        console.log(`${indent}${node.entity.constructor.name} "${node.name}"`);
    } else {
        console.log(`${indent}GROUP "${node.name}"`);
    }
    for (const child of node.childNodes) {
        visitWithTypeCheck(child, depth + 1);
    }
}

visitWithTypeCheck(scene.rootNode);

instanceof Mesh là cách an toàn nhất để xác nhận thực thể là một polygon mesh trước khi truy cập controlPoints, polygonCount, hoặc các phần tử vertex.


Bước 5: Lọc các nút theo loại thực thể

Để thu thập chỉ các nút chứa lưới mà không in toàn bộ cây, hãy sử dụng một bộ tích lũy đệ quy:

import { Mesh } from '@aspose/3d/entities';

function collectMeshes(
    node: any,
    results: Array<{ name: string; mesh: Mesh }> = []
): Array<{ name: string; mesh: Mesh }> {
    if (node.entity instanceof Mesh) {
        results.push({ name: node.name, mesh: node.entity as Mesh });
    }
    for (const child of node.childNodes) {
        collectMeshes(child, results);
    }
    return results;
}

const meshNodes = collectMeshes(scene.rootNode);
console.log(`Found ${meshNodes.length} mesh node(s)`);

Hàm chấp nhận một tùy chọn results mảng để các caller có thể điền trước nó nhằm hợp nhất kết quả qua nhiều subtree.


Bước 6: Thu thập tất cả các Mesh và in số lượng đỉnh

Mở rộng bộ thu thập để in thống kê cho mỗi lưới:

import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';

function collectMeshes(node: any, results: Array<{name: string, mesh: Mesh}> = []) {
    if (node.entity instanceof Mesh) {
        results.push({ name: node.name, mesh: node.entity as Mesh });
    }
    for (const child of node.childNodes) {
        collectMeshes(child, results);
    }
    return results;
}

const scene = new Scene();
scene.open('model.obj', new ObjLoadOptions());

const meshes = collectMeshes(scene.rootNode);
for (const { name, mesh } of meshes) {
    console.log(`${name}: ${mesh.controlPoints.length} vertices, ${mesh.polygonCount} polygons`);
}

Ví dụ đầu ra cho một cảnh có hai lưới:

Cube: 8 vertices, 6 polygons
Sphere: 482 vertices, 480 polygons

<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”

<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>

Mẹo và Thực hành Tốt nhất

  • Luôn kiểm tra null node.entity trước khi truy cập các thuộc tính đặc thù của thực thể. Nhiều nút là các nút nhóm thuần túy không mang thực thể nào.
  • Sử dụng instanceof trên constructor.name để kiểm tra kiểu trong các luồng logic. instanceof là an toàn khi tái cấu trúc; so sánh chuỗi trên constructor.name bị lỗi khi minification.
  • Duyệt qua for...of trên childNodes: iterable xử lý mọi kích thước mảng một cách an toàn. Tránh sử dụng chỉ mục số để đảm bảo khả năng tương thích trong tương lai.
  • Tránh thay đổi cây trong quá trình duyệt: không thêm hoặc xóa nút trong lời gọi đệ quy. Thu thập kết quả trước, sau đó mới sửa đổi.
  • Truyền một mảng kết quả làm tham số: điều này tránh việc cấp phát mảng mới trong mỗi lời gọi đệ quy và giúp dễ dàng hợp nhất kết quả của các nhánh con.

Các vấn đề thường gặp

Triệu chứngNguyên nhânKhắc phục
childNodes có độ dài bằng 0 trên rootNodeMô hình chưa được tảiĐảm bảo scene.open() hoàn thành mà không có lỗi trước khi duyệt
node.entity instanceof Mesh không bao giờ đúngSai Mesh đường dẫn importImport Mesh từ @aspose/3d/entities, không phải từ @aspose/3d gốc
Việc duyệt bỏ lỡ các lưới lồng nhauKhông đệ quy vào tất cả các nút conĐảm bảo lời gọi đệ quy bao phủ mọi phần tử trong node.childNodes
mesh.controlPoints.length là 0Lưới đã tải nhưng không chứa hình họcKiểm tra nguồn OBJ để tìm các nhóm trống; sử dụng mesh.polygonCount như một kiểm tra phụ
Tràn ngăn xếp trên các cây phân cấp sâuCây cảnh rất sâu (hàng trăm cấp độ)Thay thế đệ quy bằng một ngăn xếp rõ ràng sử dụng Array.push / Array.pop

Câu hỏi thường gặp

scene.rootNode bản thân nó có mang một thực thể không? Không. Nút gốc là một container được thư viện tạo tự động. Nó không có thực thể. Hình học và các đối tượng cảnh khác của bạn tồn tại trên các nút con, cách đây một hoặc nhiều cấp độ. rootNode.

Sự khác nhau giữa node.entitynode.entities? node.entity giữ thực thể chính duy nhất (trường hợp phổ biến). Một số tệp FBX và COLLADA cũ hơn có thể tạo ra các nút với nhiều thực thể đính kèm; trong trường hợp đó node.entities (số nhiều) cung cấp danh sách đầy đủ.

Tôi có thể duyệt theo thứ tự rộng (breadth-first) thay vì sâu (depth-first) không? Có. Sử dụng một hàng đợi thay vì gọi đệ quy: đẩy scene.rootNode vào một mảng, sau đó shift và xử lý các nút trong khi đẩy mỗi nút childNodes vào cuối hàng đợi.

Có phải scene.open() đồng bộ không? Có. scene.open()scene.openFromBuffer() cả hai đều chặn luồng gọi cho đến khi tệp được phân tích hoàn toàn. Đặt chúng trong một luồng worker nếu bạn cần giữ vòng lặp sự kiện phản hồi.

Làm sao tôi lấy vị trí không gian thế giới từ một nút? Đọc node.globalTransform; nó trả về một đối tượng chỉ đọc GlobalTransform với ma trận không gian thế giới, được tạo thành từ tất cả các biến đổi của tổ tiên. Đối với phép tính ma trận rõ ràng, gọi node.evaluateGlobalTransform(false).

Các loại thực thể nào có thể có ngoài Mesh? Camera, Light, và các thực thể khung xương/xương tùy chỉnh. Kiểm tra node.entity.constructor.name hoặc sử dụng instanceof với lớp cụ thể được nhập từ @aspose/3d.

Xem thêm

 Tiếng Việt