Praca z grafem sceny

Cała zawartość 3D w Aspose.3D FOSS dla TypeScript znajduje się wewnątrz Scene obiekt uporządkowany jako drzewo Node obiektów. Zrozumienie tej hierarchii jest podstawą do budowania, ładowania i przetwarzania dowolnego pliku 3D.

Instalcja

Zainstaluj pakiet z npm przed uruchomieniem jakiegokolwiek kodu w tym przewodniku:

npm install @aspose/3d

Upewnij się, że Twój tsconfig.json zawiera "module": "commonjs" i "moduleResolution": "node" dla prawidłowego rozwiązywania importów podścieżek.

Koncepcje grafu sceny

Graf sceny ma trzy poziomy:

PoziomKlasaRola
ScenaSceneKontener najwyższego poziomu. Przechowuje rootNode, animationClips, oraz assetInfo.
WęzełNodeNazwany węzeł drzewa. Może mieć węzły potomne, encję, transformację i materiały.
EncjaMesh, Camera, Light, …Zawartość dołączona do węzła. Węzeł może mieć maksymalnie jedną encję.

Łańcuch dziedziczenia głównych elementów budulcowych jest następujący:

A3DObject
  └─ SceneObject
       ├─ Node          (tree structure)
       └─ Entity
            └─ Geometry
                 └─ Mesh   (polygon geometry)

scene.rootNode jest tworzony automatycznie. Nie tworzysz go ręcznie; tworzysz pod nim węzły potomne.

Step 1: Create a Scene

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

const scene = new Scene();
console.log(scene.rootNode.name); // '' (empty string — the root node is created with no name)

Nowy Scene zaczyna się od pustego węzła głównego i braku klipów animacji. Tworzysz zawartość, dołączając węzły potomne.

Krok 2: Dodaj węzły potomne

Użyj createChildNode() aby rozbudować drzewo. Metoda zwraca nowy Node, więc możesz łańcuchowo wywoływać kolejne wywołania z dowolnego poziomu:

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

const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');

console.log(scene.rootNode.childNodes.length); // 1 (parent)
console.log(parent.childNodes.length);         // 1 (child)

Nazwy węzłów są dowolnymi ciągami znaków. Nie muszą być unikalne, ale używanie znaczących nazw ułatwia debugowanie kodu przeglądającego strukturę.

Krok 3: Utwórz Mesh i ustaw wierzchołki

Mesh jest podstawową klasą geometrii. Dodaj pozycje wierzchołków, używając push Vector4 wartości do mesh.controlPoints, a następnie wywołaj createPolygon() aby zdefiniować faces przez indeksy wierzchołków:

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

const scene = new Scene();
const child = scene.rootNode.createChildNode('parent').createChildNode('child');

const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3); // quad face using all four vertices

child.entity = mesh;

console.log(mesh.controlPoints.length); // 4
console.log(mesh.polygonCount);         // 1

Vector4 używa współrzędnych jednorodnych: w komponent to 1 dla pozycji i 0 dla wektorów kierunkowych.

Step 4: Set Node Transforms

Każdy węzeł ma transform właściwość z translation, rotation, i scaling. Ustaw translation aby przesunąć węzeł względem jego rodzica:

import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';

const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');

const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;

child.transform.translation = new Vector3(2.0, 0.0, 0.0);

globalTransform zapewnia macierz przekształcenia w przestrzeni światowej (tylko do odczytu), obliczaną przez konkatenację wszystkich przekształceń przodków.

Krok 5: Przeglądaj drzewo

Napisz funkcję rekurencyjną, aby odwiedzić każdy węzeł. Sprawdź node.entity i node.childNodes na każdym poziomie:

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

traverse(scene.rootNode);

Dla powyżej utworzonej hierarchii, wynik będzie:

 [none]
  parent [none]
    child [Mesh]

Nazwa węzła głównego jest pustym ciągiem, ponieważ Scene tworzy go bez argumentu nazwy.

Zawsze zabezpieczaj dostęp do encji sprawdzając, czy nie jest null, przed rzutowaniem na konkretny typ. Nie każdy węzeł posiada encję.

Krok 6: Zapisz do glTF lub GLB

Użyj GltfSaveOptions aby kontrolować format wyjścia. Ustaw binaryMode = true aby wygenerować pojedynczy, samodzielny .glb plik; pozostaw go false dla JSON .gltf + .bin para sidecar:

import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';

const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');

const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;

child.transform.translation = new Vector3(2.0, 0.0, 0.0);

const saveOpts = new GltfSaveOptions();
saveOpts.binaryMode = true;                             // write a single .glb file
scene.save('scene.glb', GltfFormat.getInstance(), saveOpts);

console.log('Scene saved to scene.glb');

Przekaż GltfFormat.getInstance() jako argument formatu, aby biblioteka używała właściwego enkodera niezależnie od rozszerzenia pliku.

Wskazówki i najlepsze praktyki

  • Użyj createChildNode() zamiast konstruować Node bezpośrednio: createChildNode() automatycznie łączy relację rodzic-dziecko i rejestruje węzeł w drzewie.
  • Sprawdź node.entity przed dostępem do właściwości encji: wiele węzłów (węzły grupowe, kości, lokalizatory) nie posiada encji. Zawsze zabezpieczaj je sprawdzeniem na null lub instanceof testem.
  • Ustaw translation na węzłach potomnych, a nie na wierzchołkach siatki: modyfikowanie transform.translation jest niedestrukcyjne i można je łączyć z transformacjami rodzica.
  • Preferuj binaryMode = true dla GLB: pojedynczy .glb plik jest łatwiejszy do dystrybucji, ładowania w przeglądarkach i importowania do silników gier niż podzielony .gltf + .bin format.
  • Przejdź przez for...of nad childNodes: unikaj indeksowania numerycznego; użyj iterowalnego bezpośrednio dla przyszłej kompatybilności.

Typowe problemy

ObjawPrawdopodobna przyczynaNaprawa
child.entity = mesh nie ma wpływu na eksportJednostka przypisana do niewłaściwego poziomu węzłaPrzypisz entity do węzła liścia, a nie do węzła grupy
node.entity zawsze jest nullTylko sprawdzanie rootNode samRekurencja w node.childNodes; rootNode zazwyczaj nie ma encji
Transform nie jest odzwierciedlony w przeglądarce GLBglobalTransform nie zaktualizowanoglobalTransform jest obliczane przy zapisie; ustaw transform.translation przed wywołaniem scene.save()
GLB generuje oddzielny .bin sidecarbinaryMode domyślnie ustawione na falseUstaw saveOpts.binaryMode = true

Zobacz także

 Polski