Three.ez - library for events, drag & drop, binding, focus management, smart rendering, tweening and more

three.ez

Simplify your three.js application development with three.ez!

Extend the functionalities of Object3D and Scene classes, making their usage more straightforward, and introduce utility classes.

import { Scene, Mesh, BoxGeometry, MeshNormalMaterial } from 'three';
import { Main, PerspectiveCameraAuto } from '@three.ez/main';

const box = new Mesh(new BoxGeometry(), new MeshNormalMaterial());
box.draggable = true; // make it draggable
box.on('animate', (e) => box.rotateX(e.delta).rotateY(e.delta * 2)); // animate it every frame
box.on(['pointerover', 'pointerout'], (e) => box.scale.setScalar(e.type === 'pointerover' ? 1.5 : 1));

const scene = new Scene().add(box);
const main = new Main(); // init inside the renderer, and handle events, resize, etc
main.createView({ scene, camera: new PerspectiveCameraAuto(70).translateZ(1) }); // create the view to be rendered

This library has only one dependency: three.js r151+.

:white_check_mark: Why three.ez?

  • Program the logic of your Object3D more quickly and intuitively
  • Less code and cleaner
  • Streamlined rendering
  • Declarative and imperative programming
  • Compatible with your three.js code and external libraries
  • Easy to learn
  • High performance

:key: Key Features

:sparkles: Event Programming

Add interactions to Object3D through programmable events, similar to DOM events, including a propagation system.

See events list here: Interaction, Miscellaneous, Update.

const box = new Mesh(geometry, material);
box.on('click', (e) => e.stopPropagation());
box.on('animate', (e) => console.log('animate'));
box.on('positionchange', () => console.log('position changed'));

:fire: Drag and Drop

Integrate drag and drop functionality. The drag is cancelled by pressing ESC.

const box = new Mesh(geometry, material);
box.draggable = true;
box.findDropTarget = true;
box.on('drag', (e) => console.log(`new position: ${e.position}`));

const plane = new Mesh(geometry, material);
plane.on('drop', (e) => console.log(`obj dropped on this: ${e.relatedTarget}`));

:rocket: Focus and Blur

Enhance interactivity with focus and blur events.

const box = new Mesh(geometry, material);
box.focusable = true; // default is true
box.on('focus', (e) => console.log('focused'));
box.on('blur', (e) => console.log('focus lost'));

:medal_sports: Object3D Property Binding

Streamline the management of Object3D properties.

const box = new Mesh(geometry, material);
box.bindProperty('visible', () => box.parent?.enabled); 

:scissors: Automatic Resize Handling

Automatically resizes the Renderer, Camera, and EffectComposer.

Utilize the viewportResize event to easily set the resolution for custom shaders.

const line = new Line2(geometry, material);
line.on('viewportresize', (e) => material.resolution.set(e.width, e.height));

:bulb: Smart Rendering

Optimize performance by rendering frames only when necessary, reducing computational overhead.

Automatically identifies changes in position, scale, rotation, visibility, focus, blurring and addition or removal of objects.

const scene = new Scene();
scene.activeSmartRendering();

const box = new Mesh(new BoxGeometry(), new MeshLambertMaterial({ color: 'green' }));
box.draggable = true; // if you drag the frame, it automatically detects changes and renders the frame

box.material.color.set('yellow');
box.needsRender = true; // necessary because color change cannot be automatically detected

:trophy: Simplified Multiple Rendering

Effortlessly manage rendering for multiple scenes or viewports within a single canvas.

const main = new Main(); 
main.createView({ scene, camera, viewport: { left: 0, bottom: 0, width: 0.5, height: 1 } });
main.createView({ scene, camera, viewport: { left: 0.5, bottom: 0, width: 0.5, height: 1 } });

:hammer_and_wrench: Asset Management

Efficiently load and preload the assets for your 3D projects.

load:

const audioBuffer = await Asset.load(AudioLoader, 'audio.mp3', onProgressCallback, onErrorCallback);

preload:

// soldier.js
Asset.preload(GLTFLoader, 'https://threejs.org/examples/models/gltf/Soldier.glb');

export class Soldier extends Group {
  constructor() {
    super();
    const gltf = Asset.get('https://threejs.org/examples/models/gltf/Soldier.glb');
    this.add(...gltf.scene.children);
  }
}

// main.js
await Asset.preloadAllPending({ onProgress: (e) => console.log(e * 100 + '%'), onError: (e) => console.error(e) });
const main = new Main();
const soldier = new Soldier();

:movie_camera: Tweening

Create smooth animations effortlessly with built-in tweening.

box.tween().by(1000, { position: new Vector3(0, 0.5, 0) }, { easing: 'easeInOutBack' }).yoyoForever().start();

new Tween(box)
  .by(2000, { scale: 1, rotation: new Euler(Math.PI * 2, Math.PI, 0) }, { easing: 'easeOutElastic' })
  .delay(200)
  .to(1000, { scale: 1 }, { easing: 'easeOutBounce' })
  .start();

:gear: Raycasting Customisable

Choose between continuous or mouse movement-based raycasting, optimizing intersection operations.

Set which objects to intersect from the main raycasting.

const scene = new Scene();
scene.continuousRaycasting = true; // default is false

const box = new Mesh(geometry, material);
box.interceptByRaycaster = false; // default is true

:dart: Hitbox Functionality

Leverage hitboxes for customized intersections or simplified calculations.

const ring = new Mesh(new RingGeometry(1, 1.5), new MeshBasicMaterial());
ring.hitboxes = [new Hitbox(new CircleGeometry(1.5))]; // intercept also inside the ring

:100: Simplified InstancedMesh

Manage InstancedMesh instances with the ease of working with Object3D, simplifying creation and manipulation, including frustum culling.

const myInstancedMesh = new InstancedMesh2(geometry, material, count, (obj, index) => {
  obj.position.x += index;
  obj.scale.setScalar(2);
  obj.quaternion.random();
  obj.forceUpdateMatrix();
});

// How to handle instances
myInstancedMesh.instances[0].visible = false;
myInstancedMesh.instances[1].draggable = true;
myInstancedMesh.instances[2].rotateOnWorldAxis(xAxis, Math.PI);
myInstancedMesh.instances[2].updateMatrix();

:mag: Query

Find and select Object3D using powerful query selectors.

scene.querySelectorAll('Mesh'); // Selects all the meshes in the scene.
scene.querySelectorAll('[name=box]'); // Selects all Object3D that have 'box' as their name.
scene.querySelectorAll('[name*=box]'); // Selects all Object3D that have 'box' anywhere in their name.
scene.querySelectorAll('Mesh.even'); // Selects meshes with both 'Mesh' type and 'even' tag.
scene.querySelectorAll('Group .even'); // Selects all Object3D with 'even' tag that are children of a 'Group'.
scene.querySelectorAll('Group > .even'); // Selects all direct children with 'even' tag under a 'Group'.
scene.querySelectorAll('Mesh, SkinnedMesh'); // Selects all meshes and skinned meshes in the scene.

:arrow_down: Installation

You can install it via npm using the following command:

npm install @three.ez/main

Or can import it from CDN:

<script type="importmap">
{
  "imports": {
    "three": "https://unpkg.com/three@0.158.0/build/three.module.js",
    "three/examples/jsm": "https://unpkg.com/three@0.158.0/examples/jsm/",
    "@three.ez/main": "https://unpkg.com/@three.ez/main@0.4.10/bundle.js"
  }
}
</script>

:technologist: Live Examples

These examples use vite, and some mobile devices may run out of memory. However, there is one example without it.

Examples Collection

:books: Documentation

The tutorial is available here (work in progress).

The API documentation is available here.

:handshake: Contributing

Any help is highly appreciated. If you would like to contribute to this package or report problems, feel free to open a bug or pull request.

:grey_question: Questions?

If you have questions or need assistance, you can ask on our discord server.

6 Likes

Thanks for sharing your library. It looks like it has a lot of useful features.
I always admire when people share their work … especially when the API is well documented.

Smart rendering is good, I also have some form of it, for two end-user’s reasons:

  • less noise and heating when 3D is run on a laptop
  • more battery when 3D is run on a smartphone

Is this list (position, scale,…) the complete list? If it is, you should also monitor for changes in materials, changes in environment …

Also, when in VR mode, optimizing scene rendered could be tricky. I found this the hard way – I tried to restrict FPS by skipping some of the render requests – and the result was strong photoepilepsy inducing flickering.

3 Likes

Thank you very much for the feedback!

Currently only those changes are automatically detected and that is because the position, scale, rotation and visibility are overridden, so the cost is a single extra operation for changing the needsRender flag.

There is not, currently, an automatism that controls all object changes (materials, geometries, etc.) because it might slow down performance, so in case I do, it will probably be a separate package.

It is, however, possible to change the flag of needsRender manually by editing it on any Object3D that is added to a scene, as in this example:

const draggableBox = new Mesh(new BoxGeometry(0.2, 0.2, 0.2), new MeshLambertMaterial({ color: 'green' }));
draggableBox.draggable = true;
draggableBox.on(['pointerenter', 'pointerleave'], function(e) {
  this.material.color.set(e.type === 'pointerenter' ? 'yellow' : 'green');
  this.needsRender = true; // necessary because color change cannot be auto detected
});

const scene = new Scene();
scene.activeSmartRendering(); // auto detect drag moves in this case
scene.add(new AmbientLight(), new DirectionalLight().translateZ(1), draggableBox);

const main = new Main();
main.createView({ scene, camera: new PerspectiveCameraAuto(70).translateZ(1) });

I wrote this library to simplify writing examples or 3d viewers, although it can be used for something more complex, but I did not consider VR. I will definitely try it!

Soon I will add more features such as fps setting, continuous raycasting frequency, multidrag and multifocus.

1 Like

Cool library, i made a little experiment with it:

It’s very useful and easy to use, especially the instaced mesh implementation, however i’m waiting for the complete documentation.

1 Like

I added some documentation, and released r4.
One of the new features is the Asset class, which allows you to load/preload resources more easily.

Preloading in a single file

await Asset.loadAll({ 
  onProgress: (e) => console.log(e * 100 + '%'),
  onError: (e) => console.error(e)
}, {
  loader: TextureLoader,
  paths: ['texture.jpg', 'texture2.jpg'],
}, {
  loader: AudioLoader,
  paths: ['assets/win.mp3'],
}, {
  loader: GLTFLoader,
  paths: ['model.glb'],
});

// now assets are loaded
const gltf = Asset.get<GLTF>('model.glb');
const texture = Asset.get<Texture>('texture.jpg');

Preloading in multiple files

soldier.ts

Asset.preload(GLTFLoader, 'https://threejs.org/examples/models/gltf/Soldier.glb');

export class Soldier extends Group {
  constructor() {
    super();
    const gltf = Asset.get<GLTF>('https://threejs.org/examples/models/gltf/Soldier.glb');
    this.add(...gltf.scene.children);
  }
}

main.ts

await Asset.preloadAllPending({ onProgress: (e) => console.log(e * 100 + '%') });
// now assets are loaded
const main = new Main();

Asset API
Asset Tutorial

1 Like