Как да обходим 3D граф на сцената в TypeScript

Как да обходим 3D граф на сцената в TypeScript

Сценичният граф в Aspose.3D FOSS за TypeScript е дърво от Node обекти, коренувано в scene.rootNode. Обхождането е рекурсивно: всеки възел предоставя childNodes итерируем и опционално свойство entity. Това ръководство показва как да се обходим цялото дърво, да идентифицираме типове на обекти и да съберем статистика за мрежите.

Предварителни условия

  • Node.js 18 или по-нова версия
  • TypeScript 5.0 или по-нова версия
  • @aspose/3d инсталиран

Ръководство стъпка по стъпка

Стъпка 1: Инсталиране и импортиране

Инсталирайте пакета:

npm install @aspose/3d

Импортирайте класовете, използвани в това ръководство:

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

Scene и Mesh са основните класове. ObjLoadOptions се използва в примера за зареждане; заменете съответния клас за опции за други формати.


Стъпка 2: Зареждане на сцена от файл

Създайте Scene и извикайте scene.open() с път към файл. Откриването на формата е автоматично въз основа на бинарните магически числа, така че не е необходимо да посочвате формата за файлове GLB, STL или 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}`);

Можете също да заредите от Buffer в паметта, използвайки scene.openFromBuffer(buffer, options); полезно в безсървърни конвейери, където дисковият I/O не е наличен.


Стъпка 3: Напишете рекурсивна функция за обхождане

Рекурсията върху childNodes е стандартният шаблон. Функцията посещава всеки възел в дълбочина първо:

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);

За сцена с една мрежа, наречена Cube, изходът ще изглежда така:

[-] 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.entity е null за групови възли, кости и локатори. Проверката constructor.name работи за всеки тип обект: Mesh, Camera, Light и т.н.


Стъпка 4: Достъп до типа на обекта за всеки възел

За да предприемете действие въз основа на типа на обекта, използвайте проверка instanceof след 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 е най‑сигурният начин да се потвърди, че обектът е полигонална мрежа, преди да се достъпват controlPoints, polygonCount или елементите на върховете.


Стъпка 5: Филтриране на възлите по тип на обекта

За да съберете само възлите, съдържащи мрежа, без да отпечатвате цялото дърво, използвайте рекурсивен акумулатор:

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)`);

Функцията приема незадължителен results масив, за да могат извикващите да го предварително попълнят за обединяване на резултати от множество поддървета.


Стъпка 6: Събиране на всички мрежи и отпечатване на броя на върховете

Разширете събирача, за да отпечатва статистика за всеки mesh:

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`);
}

Примерен изход за сцена с две мрежи:

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>

Съвети и най‑добри практики

  • Винаги проверявайте за null node.entity преди да достъпвате свойства, специфични за обекта. Много възли са чисто групови възли, които не носят обект.
  • Използвайте instanceof вместо constructor.name за проверки на типове в логически пътища. instanceof е безопасен при рефакторинг; сравняването на низове върху constructor.name се повреди при минифициране.
  • Обхождайте чрез for...of вместо childNodes: итераторът безопасно обработва всички размери на масиви. Избягвайте числово индексиране за бъдеща съвместимост.
  • Избягвайте модифицирането на дървото по време на обхождане: не добавяйте и не премахвайте възли вътре в рекурсивния повик. Събирайте резултатите първо, след това модифицирайте.
  • Подайте масив с резултати като параметър: това избягва създаването на нов масив при всяко рекурсивно извикване и улеснява сливането на резултатите от поддърветата.

Чести проблеми

СимптомПричинаПоправка
childNodes има нулева дължина на rootNodeМоделът не е зареденУверете се, че scene.open() е завършено без грешка преди обхождане
node.entity instanceof Mesh никога не е вярноГрешен път за импорт на MeshИмпортирайте Mesh от @aspose/3d/entities, а не от корена @aspose/3d
Обхождането пропуска вложени мрежиНе рекурсира към всички децаУверете се, че рекурсивният повик покрива всеки елемент в node.childNodes
mesh.controlPoints.length е 0Мрежата е заредена, но не съдържа геометрияПроверете източника OBJ за празни групи; използвайте mesh.polygonCount като вторична проверка
Препълване на стека при дълбоки йерархииМного дълбоко дърво на сцената (стотини нива)Заменете рекурсията с явен стек, използвайки Array.push / Array.pop

Често задавани въпроси

Носи ли scene.rootNode сам по себе си ентитет?
Не. Коренният възел е контейнер, създаден автоматично от библиотеката. Той няма ентитет. Вашата геометрия и други обекти от сцената се намират в дъщерни възли един или повече нива под rootNode.

Каква е разликата между node.entity и node.entities?
node.entity съдържа единствения основен обект (обичайният случай). Някои по‑стари FBX и COLLADA файлове могат да създадат възли с множество прикрепени обекти; в този случай node.entities (множество) предоставя пълния списък.

Мога ли да обхождам в широчинен ред вместо в дълбочинен?
Да. Използвайте опашка вместо рекурсивно извикване: поставете scene.rootNode в масив, след това shift‑нете и обработете възлите, като поставяте childNodes в края на опашката за всеки възел.

scene.open() синхронен ли е?
Да. scene.open() и scene.openFromBuffer() блокират извикващата нишка, докато файлът не бъде напълно анализиран. Обвийте ги в работна нишка, ако трябва да запазите отзивчивостта на събитийния цикъл.

Как да получа позиции в световното пространство от възел? Прочетете node.globalTransform; тя връща само за четене GlobalTransform с матрицата в световното пространство, съставена от всички предходни трансформации. За явна матрична аритметика, извикайте node.evaluateGlobalTransform(false).

Какви типове обекти са възможни освен Mesh? Camera, Light и персонализирани скелет/костни обекти. Проверете node.entity.constructor.name или използвайте instanceof със специфичния клас, импортиран от @aspose/3d.

Виж също

 Български