Как да обходим 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);За сцена с един mesh, наречен 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: Достъп до типа на обекта за всеки възел
За да предприемете действие въз основа на типа на обекта, използвайте an 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 е най-сигурният начин да се потвърди, че обектът е polygon 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: Събиране на всички мрежи и отпечатване на броя върхове
Разширете събирача, за да отпечатва статистика за всяка мрежа:
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 import path | Импорт 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 (множ. число) предоставя пълния списък.
Мога ли да обхождам в breadth‑first ред вместо в depth‑first? Да. Използвайте опашка вместо рекурсивно извикване: push scene.rootNode в масив, след това shift и обработвайте възлите, докато push‑вате всеки възел childNodes в края на опашката.
Е scene.open() синхронен? Да. scene.open() и scene.openFromBuffer() и двете блокират извикващата нишка, докато файлът не бъде напълно анализиран. Обвийте ги в работна нишка, ако трябва да запазите отзивчивостта на цикъла на събитията.
Как да получа позиции в световното пространство от възел? Прочетете node.globalTransform; връща само за четене GlobalTransform с матрица в световното пространство, съставена от всички трансформации на предците. За изрично матрично изчисление, извикайте node.evaluateGlobalTransform(false).
Какви типове обекти са възможни освен Mesh? Camera, Light, и персонализирани скелет/костни обекти. Проверете node.entity.constructor.name или използвайте instanceof с конкретния клас, импортиран от @aspose/3d.