Jak klikat a přetahovat 3D modely v ThreeJS

Schopnost přesouvat 3D modely v projektu three.js může mít téměř nekonečné aplikace...

Modely:Stanley Creative, Johnson Martin a Gravity Jack

...tento článek vysvětlí, jak přidat tuto funkci do vaší aplikace. Přičemž také řeší složitosti, které vznikají, když se tyto 3D modely skládají z více objektů.

Tento článek je rozdělen do 2 sekcí:

A) Přesouvání jednotlivých objektů (Object3D)
B) Přesouvání předmětů s dětmi (Skupina)

Sekce "A" položí základy. Představí koncepty a funkce, které budou potřeba při přesunu 3D modelu. Pokud již dobře rozumíte přesouvání Object3D v three.js, můžete buď prolétnout tuto sekci, nebo ji jednoduše přeskočit a přejít přímo k další sekci. Sekce "B" se ponoří do toho, jak vlastně přesunout 3D model ve třech.js, a do složitosti Object3D s dětmi.

A) Přesouvání jednotlivých objektů (Object3D)

Singulární objekty ve třech.js jsou zpracovávány třídou Objects3D.
Každý jednotlivý objekt ve scéně bude vždy svým vlastním Object3D.

Některé příklady jsou vestavěné geometrické tvary, které lze snadno přidat do scény. Tyto jedinečné objekty přicházejí v široké škále tvarů, z nichž každý má několik možností přizpůsobení.

Tato část ukáže, jak přidat tyto Object3D do scény a jak s nimi potom pohybovat pomocí ovládacích prvků myši.

(Živé demo kódu této sekce lze nalézt zde:Move-Object3D. )

1) Vytvořte a nastavte scénu

Budeme potřebovat scénu three.js s kamerou, světly, rendererem, ovládacími prvky a dalšími požadovanými atributy. Zde je základní šablona, ​​ze které můžete stavět, pokud ji ještě nemáte.

2) Přidejte objekt

Pro tento příklad vytvoříme válec, ale může to být snadno jakýkoli základní tvar, který poskytuje three.js. Zde je kód, jak to udělat:

function addObject(radius, pos, color) {
  const object = new THREE.Mesh(
    new THREE.CylinderBufferGeometry(radius, radius, 10, 50),
    new THREE.MeshPhongMaterial({ color: color })
  );
  object.position.set(pos.x, pos.y, pos.z);
  object.isDraggable = true;
  scene.add(object);
};

Jak můžete vidět, const object je proměnná, do které je uložen válec Object3D. Velikost, barva a detaily jsou plně přizpůsobitelné a nemusí odpovídat tomu, co je zobrazeno.

Odtud jen nastavíme několik základních vlastností.
position je výchozí vlastnost s vestavěným set() funkce a isDraggable je uživatelská vlastnost, která byla přidána pro pozdější použití.
Jakmile nastavíme požadované vlastnosti, vše, co uděláme, je jednoduše je přidat do scény takto...

addObject(8, { x: 0, y: 6, z: 0 }, '#FF0000');

3) Holding proměnná pro objekt

Ve scéně můžeme mít více objektů; chceme se však pohybovat pouze po jednom. Jeden snadný přístup k tomu je vytvořit proměnnou kontejneru, která bude obsahovat objekt, který chceme přesunout; s tímto kontejnerem pak můžeme manipulovat v globálním měřítku, aniž by každá z našich funkcí musela vědět, který konkrétní objekt byl vybrán. Funkce místo toho pouze provede obecné změny v kontejneru, které „stečou“ dolů k objektu. To uvidíme v akci v dalším kroku.
Pro tento příklad jsem tento kontejner nazval draggableObject .

// Global variables
Let draggableObject;

4) kliknutí myší posluchač událostí

Abychom mohli vybrat objekt, budeme potřebovat mít posluchače, který bude sledovat kliknutí myší.

window.addEventListener('click', event => {
  // If 'holding' object on-click, set container to <undefined> to 'drop’ the object.
  if (draggableObject) {
    draggableObject= undefined;
    return;
  }

  // If NOT 'holding' object on-click, set container to <object> to 'pick up' the object.
  clickMouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  clickMouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(clickMouse, camera);
  const found = raycaster.intersectObjects(scene.children, true);
  if (found.length && found[0].object.isDraggable) {
    draggableObject = found[0].object;
  }
});

Dobře, děje se toho hodně, tak si to pojďme rozebrat.
Nejprve musíme pochopit, jak se náš objekt bude pohybovat. Pro tento tutoriál na první kliknutí vyzvedneme objekt. Jakmile držíme objekt, můžeme pohybovat myší kdekoli v okně, abychom objekt posunuli. Poté druhým kliknutím objekt „upustíme“.

S tímto pochopením se podívejme na kód. První zkratový příkaz if má zvládnout pád. Pokud nedržíme předmět, pokračujeme v určování, který předmět zvednout po kliknutí (pokud existuje nějaký platný předmět) .

K nalezení objektu používáme raycaster . Funguje to tak, že se vytvoří čára začínající od pozice kamery a putuje do místa kliknutí myší a pak pokračuje přes všechny objekty, dokud nedosáhne konce scény. Z tohoto důvodu potřebujeme získat x a y umístění kliknutí myší, abyste mohli vytvořit tuto čáru.

Nakonec toto raycaster vrátí pole všech objektů, kterými prošel, a prázdné pole, pokud neprošlo žádnými objekty. Abychom určili, který objekt chceme přesunout, musíme zkontrolovat dvě věci. Byly nalezeny nějaké předměty? found.length a je první objekt v poli přetahovatelný? found[0].object.isDraggable . (Tady vstupuje do hry vlastní vlastnost z kroku 1) . Pokud máte podlahu, stěny, strop nebo jiné objekty, které nechcete, aby bylo možné přetahovat, můžete jednoduše nastavit tuto logickou hodnotu jako false a funkce zde končí.

Nyní, když jsme se dostali na konec funkce a našli platný objekt k přesunutí, musíme jej uložit do proměnné kontejneru draggableObject . Nyní můžeme upravit pozici tohoto kontejneru v jiné funkci.

5) pohyb myší posluchač událostí

Než budeme moci přesunout kontejner, musíme být schopni sledovat polohu myši. Tento základní posluchač to udělá. S těmito informacemi můžeme objekt znovu vykreslit, když s ním pohybujeme po dráze myši.

window.addEventListener('mousemove', event => {
  moveMouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  moveMouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
});

6) Vytvořte funkci pro přetažení objektu

Jsme téměř hotovi. Potřebujeme pouze funkci, která nám umožní přesunout vybraný objekt, který se nachází v draggableObject . Tato funkce může využívat posluchače pohybu myši, který jsme právě vytvořili.

function dragObject() {
  // If 'holding' an object, move the object
  if (draggableObject) {
  const found = raycaster.intersectObjects(scene.children);
  // `found` is the metadata of the objects, not the objetcs themsevles  
    if (found.length) {
      for (let obj3d of found) {
        if (!obj3d.object.isDraggablee) {
          draggableObject.position.x = obj3d.point.x;
          draggableObject.position.z = obj3d.point.z;
          break;
        }
      }
    }
  }
};

Jak vidíte, první věc, kterou děláme, je kontrola, zda je kontejner prázdný (undefined ) nebo pokud obsahuje objekt. Pokud obsahuje předmět, musíme s ním být schopni pohybovat po podlaze.

Vytvoříme další raycaster zkontrolovat všechny křižovatky a zda je země stále pod objektem, který chceme přesunout. V podstatě jde o sledování pohybu myši pomocí moveMouse a nalezení místa, kde se umístění myši protíná s jinými objekty (v tomto případě podlaha s isDraggablee = false ). Poté aktualizuje polohu kontejnerů pomocí těchto výsledků, které zase aktualizují objekt v něm.

To je skvělé a přesně to, co chceme, ale aby tato funkce fungovala, musí být neustále volána. Jinak nebudeme mít živou reprezentaci objektu, který se přetahuje. Řešení je vlastně super jednoduché. Vše, co musíme udělat, je umístit tuto funkci do události posluchače myši, jako tak…

window.addEventListener('mousemove', event => {
  dragObject();
  moveMouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  moveMouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
});

A tím jsme hotovi, nyní můžeme zvedat, držet a pouštět jakékoli předměty na scéně. Gratulujeme!

B) Přesouvání předmětů s dětmi (Skupina)

Model:Stanley Creative

Tato část nahradí výchozí objekt geometrie, který poskytuje three.js, 3D modelem dle vlastního výběru. V případě tohoto příkladu to bude ze zdroje místního adresáře.

Velmi důležité je poznamenat, že 3D model není jediný objekt 3D jako jsou tvary z výše uvedené části. Místo toho jsou to Skupiny s více podřízenými objekty Object3D. I ty nejjednodušší modely budou mít určitou složitost. Proto je tato sekce tak důležitá.

(Živé demo kódu této sekce lze nalézt zde:Move-Group. )

1) Nastavení a vytvoření scény

Ujistěte se, že už máte základy aplikace three.js na místě. _Vraťte se do sekce A nebo navštivte živé demo, pokud ještě nemáte nic vytvořeno.

2) Přidejte model

Podobné jako addObject() potřebujeme takovou, která bude schopna načíst naše aktiva do scény, kterou jsme vytvořili.

function addModel(pos) {
  const loader = new GLTFLoader();
  loader.load(`res/saturnV/scene.gltf`, (gltf) => {
    const model = gltf.scene;
    model.position.set(pos.x, pos.y, pos.z);
    model.isDraggable = true;
    scene.add(model);
  });
}

První věc, které si všimnete, je, že tato funkce využívá GLTFLoader . Ujistěte se, že jste to nějakým způsobem importovali do svého programu. Zde se můžete podívat na pokyny k instalaci nebo se podívat, jak jsem to udělal v ukázce.

Zavaděčem jednoduše říkáme, odkud má soubory načíst. V případě tohoto příkladu jsou umístěny v adresáři v rámci res složka.

Jakmile je const model je naplněn, upravíme vlastnosti; ujistěte se, že zahrneme isDraggable = true a přidejte jej do scény stejně, jako jsme to udělali pro objekt v sekci A .

Jakmile je toto vytvořeno, potřebujeme pouze tuto funkci...

addModel({ x: 0, y: 6, z: 0 });

3) Objects3D vs Groups

PROBLÉM:
Pokud se pokusíte otestovat aplikaci v této aktuální fázi, s největší pravděpodobností to nebude fungovat. Existují dva problémy, se kterými se můžete potýkat.

  1. Váš model se nikdy nezvedne, a proto s ním nemůžete vůbec pohnout.
  2. Najednou lze přesunout pouze jeden kus modelu. Výsledkem je, že to roztrháte kousek po kousku.
Model:Stanley Creative

Proč je to?
Důvod těchto nežádoucích výsledků je způsoben tím, jak jsou položky modelu uloženy a jak GLTFLoader načte je do scény.

Na rozdíl od jednoduchého Object3D se modely obvykle skládají z více Object3D; někdy i stovky . Z tohoto důvodu GLTFLoader vloží všechny tyto Object3D do skupiny. Tyto skupiny fungují téměř identicky s Object3D s výjimkou zřejmého faktu, že jsou to skupiny.

POZNÁMKA: Dokonce i ten nejzákladnější z modelů, který je shodou okolností jediným Object3D (to se stává velmi zřídka). Stále bude načten jako skupina. Prostě skupina s jedním Object3D.

To vše znamená, když nastavíme const model = gltf.scene; v předchozím kroku jsme nenastavovali Object3D na const model ale Skupina. Naše skupina je tedy nyní přetahovatelná, ale jednotlivé objekty ve skupině nikoli. Aby toho nebylo málo, aktuálně naše raycaster hledá pouze Object3D a ne skupiny.

ŘEŠENÍ:
Chcete-li tento problém vyřešit, nejlepším řešením je změnit to, co vkládáme do přetahovatelného kontejneru. Musíme umístit celou skupinu do kontejneru.

Abychom toho dosáhli, musíme pochopit, že Skupiny jsou strukturovány jako Stromy. Každý Object3D v rámci skupiny nemůže mít žádné až více potomků. Z tohoto důvodu se může zkomplikovat, pokud se pokusíme získat přístup ke každému jednotlivému uzlu, takže to neuděláme. Místo toho pouze vybereme Object3D (kterýkoli z nich) v rámci skupiny, když klikneme, a pak projdeme každým rodičem, dokud nedosáhneme vrcholu. Tato horní vrstva bude skupina vytvořená pomocí GLTLoader s isDraggable = true .

K tomu použijeme addEventListener(‘click’, event… ze sekce A krok 4 výše a změňte příkaz if za raycaster najde předmět.
Takto bude vypadat kód...

const found = raycaster.intersectObjects(scene.children, true);
  if (found.length) {
  // Cycle upwards through every parent until it reaches the topmost layer (the Group)
  let current = found[0].object;
  while (current.parent.parent !== null) {
    current = current.parent;
  }
  if (current.isDraggable) {
    draggableModel = current;
  }
}

S tímto nastavením nezáleží na tom, kolik uzlů je ve stromu skupiny, nakonec se dostaneme do nejvyšší vrstvy. Zde zkontrolujeme isDraggable Boolean. Pokud je to pravda, můžeme nyní vyzvednout model a přesunout jej stejně jako předtím.

Je dobré poznamenat, že i když jsme to změnili, zde nám kód stále umožní vybírat Skupiny s jedním Object3D, stejně jako Object3D, které ve skupinách vůbec nejsou.

4) Závěr

A tím jsme všichni hotovi.
Nyní můžeme načíst naše modely do scény a přesouvat je bez ohledu na to, jak složité modely jsou. Zároveň se můžeme pohybovat po vestavěných tvarech.

Kompletní repo pro všechny výše uvedené kódy lze nalézt zde
Živé ukázky najdete zde:
Move-Object3D
Přesunout skupiny

Děkuji za přečtení.
Pokud máte nějaké dotazy nebo připomínky, neváhejte se na mě obrátit.
Moje informace:GitHub, Linkedin