Listings 3-D-Web

Listing 1: Aufbau einer einfachen 3-D-Szene in Three.js
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('main').appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
camera.position.set(0, 2, 5);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 2;
scene.add(cube);

const planeGeometry = new THREE.PlaneGeometry(20, 20);
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

const animate = () => {
    requestAnimationFrame(animate);

    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
};

animate();

---

Listing 2: Szene in Babylon.js
import * as BABYLON from 'babylonjs';

const canvas = document.getElementById('renderCanvas');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);

const camera = new BABYLON.ArcRotateCamera(
    'camera',
    -Math.PI / 2,
    Math.PI / 4,
    5,
    new BABYLON.Vector3(0, 2, 0),
    scene
);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0), scene);

const cube = BABYLON.MeshBuilder.CreateBox('cube', {}, scene);
cube.position.y = 2;
cube.material = new BABYLON.StandardMaterial('material', scene);
cube.material.diffuseColor = new BABYLON.Color3(0, 1, 0);

const plane = BABYLON.MeshBuilder.CreateGround(
    'ground',
    { width: 20, height: 20 },
    scene
);
plane.material = new BABYLON.StandardMaterial('planeMaterial', scene);
plane.material.diffuseColor = new BABYLON.Color3(1, 1, 1);

scene.registerBeforeRender(() => {
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
});

engine.runRenderLoop(() => {
    scene.render();
});

---

Listing 3: Externes Modell laden
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
loader.load(
    '/xwing.glb',
    (gltf) => {
        gltf.scene.scale.set(50, 50, 50);
        gltf.scene.position.set(5, 30, 0);

        this.scene.add(gltf.scene);

        gltf.scene.traverse((child) => {
            if (child.isMesh) {
                child.castShadow = true;
                child.receiveShadow = true;
            }
        });
    },
    function (xhr) {
        console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
    },
    function (error) {
        console.log('An error happened');
    }
);

---

Listing 4: Mit Objekten über einen Raycaster interagieren
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('main').appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
camera.position.set(0, 2, 5);

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 2;
scene.add(cube);

const planeGeometry = new THREE.PlaneGeometry(20, 20);
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

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

    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(scene.children);

    for (let i = 0; i < intersects.length; i++) {
        intersects[i].object.material.color.set(0xff0000);
    }
});

const animate = () => {
    requestAnimationFrame(animate);

    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
};

animate();

---

Listing 5: Objektinformationen mit einem Sprite-Objekt anzeigen
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
camera.position.set(0, 2, 5);

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 2;
scene.add(cube);

const planeGeometry = new THREE.PlaneGeometry(20, 20);
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

window.addEventListener('click', function (event) {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(scene.children);

    if (intersects.length > 0) {
        const firstObject = intersects[0].object;

        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 256;
        canvas.height = 90;
        ctx.font = '24px Arial';
        ctx.fillStyle = 'white';
        ctx.fillText('Type: ' + firstObject.geometry.type, 10, 30);
        ctx.fillText(
            `Pos: ${firstObject.position.x.toFixed(
            0
            )}, ${firstObject.position.y.toFixed(
            0
            )}, ${firstObject.position.z.toFixed(0)}`,
            10,
            60
        );
        const color = firstObject.material.color.getHexString();
        ctx.fillText(`Color: #${color}`, 10, 90);

        const texture = new THREE.CanvasTexture(canvas);
        const spriteMaterial = new THREE.SpriteMaterial({
            map: texture,
            color: 0xffffff,
        });
        const sprite = new THREE.Sprite(spriteMaterial);
        sprite.scale.set(2, 0.5, 1);
        sprite.position.copy(firstObject.position).add(new THREE.Vector3(0, 0, 1));
        scene.add(sprite);
    }
});

const animate = function () {
    requestAnimationFrame(animate);

    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
};

animate();

---

Listing 6: Easing-Funktion mit tween.js
import * as THREE from 'three';
import TWEEN from '@tweenjs/tween.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

const targetPosition = new THREE.Vector3(2, 0, 0);
const startPosition = new THREE.Vector3();

const tween = new TWEEN.Tween(startPosition)
    .to(targetPosition, 2000)
    .easing(TWEEN.Easing.Quadratic.InOut)
    .onUpdate(() => {
        cube.position.copy(startPosition);
    })
    .start();

const animate = () => {
    requestAnimationFrame(animate);
    TWEEN.update();
    renderer.render(scene, camera);
};

animate();

---
