jonobr1 / two.js

A renderer agnostic two-dimensional drawing api for the web.
https://two.js.org
MIT License
8.3k stars 455 forks source link

[Question] Need to better understand several ways to setup/animate in Twojs #648

Open geohuz opened 2 years ago

geohuz commented 2 years ago

Several questions to understand the process of initializing/animating objects in twojs

First of all thank you for creating this awesome library! I'm studying the examples to make twojs work in my project, specifically the webgl rendering mode. Here are some of the questions list below, although I can run the examples and make several tweaks here and there, but I believe a better understanding will make my life easier:

  1. The difference between new Two.XXXX and two.makeXXXX, when to choose which.

  2. When to use method two.render()? In home page animation example we dont have to call two.render(), instead we have the following code:

two.bind('update', update);
two.play();

It looks like there are two ways of initialize and control the animation, also in one of the way we don't need to call requestAnimationFrame, it is quite confusing because there is no documentation explains the reason behind the scene. Sorry I'm quite new to the twojs and animation library. But if there are narrative chapters explain the logic then it will become much easier to grasp.

  1. The velocity, It seems that I have to call position.add(velocity) to make the object moving. Still, I don't understand why in other way (two.bind('update', update)) we dont need to use velocity (Just calling two.play())
jonobr1 commented 2 years ago

I'll get into all of these answers as separate posts. This post will be about

The difference between new Two.XXX and two.makeXXX

two.makeXXX calls new Two.XXX internally. new Two.XXX is the foundational way to create objects that can be rendered in two.js. two.makeXXX also adds the object to the instance's scene. In the case of two.makePath and two.makeCurve it also constructs Two.Anchors for you.

e.g:

// This is...
const a = new Two.Star(x, y, innerRadius, outerRadius, sides);
two.add(a);
// ...equivalent to:
const b = two.makeStar(x, y, innerRadius, outerRadius, sides);

And for Two.Paths and curves:

const x1 = - 10;
const x2 = 0;
const x3 = 10;
const y = 0;

// This is...
const a = new Two.Path([new Two.Anchor(x1, y), new Two.Anchor(x2, y), new Two.Anchor(x3, y)]);
two.add(a);
// ...equivalent to:
const b = two.makePath(x1, y, x2, y, x3, y);

// And for curves
// This is...
const c = new Two.Path([new Two.Anchor(x1, y), new Two.Anchor(x2, y), new Two.Anchor(x3, y)]);
c.curved = true;
two.add(c);
// ...equivalent to
const d = two.makeCurve(x1, y, x2, y, x3, y);

So, essentially the make functions are shorthand methods to construct objects bound to the current instance of Two.js.

geohuz commented 2 years ago

@jonobr1 thank you!, can't wait for the new post! BTW, would you mind to put usage of clone into the post? I guess the clone will be memory efficiency?

jonobr1 commented 2 years ago

When to use method two.render()? In home page animation example we don't have to call two.render(), instead we have the following code:

Kind of like two.makeXXX and new Two.XXX, two.update calls two.render. two.update also calculates time deltas, frame counts, and resizes the canvas (if you've set fullscreen: true on construction). The time deltas and frame counts are passed through as arguments to the update handler like so:

two.bind('update', onUpdate);
let elapsed = 0;
function onUpdate(frameCount, timeDelta) {
  elapsed += timeDelta;
}

If you're not using the out of the box animation, like a physics library or just drawing one frame, then you should use two.render:

const two = new Two({
  width: 300,
  height: 200
}).appendTo(document.body);
const rect = new Two.Rectangle(two.width / 2, two.height / 2, 50, 50);
two.add(rect);
// Nothing will show up on canvas until...
two.render();
jonobr1 commented 2 years ago

The velocity, It seems that I have to call position.add(velocity) to make the object moving. Still, I don't understand why in other way (two.bind('update', update)) we don't need to use velocity (Just calling two.play())

Velocity isn't a Two.js construct, I just use Two.Vectors to store x and y steps. Regardless of the example, it's necessary to tell Two.js where everything should be at whatever time. So, if you want to make a rectangle move across the screen you can do it several ways:

const two = new Two({
  fullscreen: true,
  autostart: true
}).appendTo(document.body);

const rect = new Two.Rectangle(0, 50, 50, 50);
// First way with two.js functions
two.bind('update', onUpdate);
function onUpdate(frameCount, timeDelta) {
  rect.position.x += 1;
}
// Second way: using your own requestAnimationFrame
loop();
function loop() {
  rect.position.x += 1;
  requestAnimationFrame(loop);
}

No need to use a velocity object to move things across the screen. We just say move 1 pixel every frame. The benefit of using Two.Vector, which is what the position attribute of any two.js shape is, is that you can use its methods to make the code shorter. E.g:

const velocity = new Two.Vector(1, 0);
two.bind('update', onUpdate);

function onUpdate(frameCount, timeDelta) {
  rect.position.add(velocity);
  // Or subtract to move it to the left instead of the right
}

There's not really a "right" or "wrong" way, but many ways to achieve the same thing.

jonobr1 commented 2 years ago

would you mind to put usage of clone into the post?

Clone is a method to create an object and copy all of its current properties at the time of invocation. Again, it's shorthand. E.g:

const circle = new Two.Circle(two.width / 2, two.height / 2, 50);
const anotherCircle = circle.clone();
const yetAnotherCircle = new Two.Circle();
yetAnotherCircle.position.copy(circle.position);
yetAnotherCircle.radius = circle.radius;

Each of these instances have the same position and radius. It was just one line of code for anotherCircle because of the clone method.