כיצד לעבור על גרף סצנה תלת‑ממדי ב‑TypeScript

כיצד לעבור על גרף סצנה תלת‑ממדי ב‑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); זה שימושי בצינורות ללא שרת שבהם קלט/פלט לדיסק אינו זמין.


שלב 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=“העתק קוד”

<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: אסוף את כל הרשתות והדפס ספירות קודקודים

הרחב את האוסף כדי להדפיס סטטיסטיקות לכל רשת:

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=“העתק קוד”

<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 למערך, ואז הזז ועבד צמתים תוך כדי דחיפת childNodes של כל צומת לסוף התור.

האם scene.open() סינכרוני? כן. scene.open() ו-scene.openFromBuffer() חוסמים את חוט הקריאה עד שהקובץ מפוענח במלואו. עטוף אותם בחוט עובד אם אתה צריך לשמור על לולאת האירועים מגיבה.

איך אני מקבל מיקומי מרחב‑עולם מצומת?
קרא node.globalTransform; הוא מחזיר GlobalTransform לקריאה בלבד עם מטריצת מרחב‑עולם, המורכבת מכל ההמרות של האבות. לצורך חישובי מטריצה מפורשים, קרא node.evaluateGlobalTransform(false).

אילו סוגי ישויות אפשריים בנוסף לMesh?
Camera, Light, ויישויות שלד/עצם מותאמות. בדוק node.entity.constructor.name או השתמש בinstanceof עם המחלקה הספציפית שיובאה מ@aspose/3d.

ראה גם

 עברית