sinisterchipmunk / jax

Framework for creating rich WebGL-enabled applications using JavaScript and Ruby
http://jaxgl.com
MIT License
96 stars 16 forks source link

More TLC for Jax.Camera #90

Closed sinisterchipmunk closed 11 years ago

sinisterchipmunk commented 11 years ago

Though I know it'll never be perfect, I was really pretty happy with the new implementation of Jax.Camera until I saw this:

# warning : using cam.direction as third parameter here does not yield same (nor expected) result
cam.direction = vec3.transformQuat vec3.create(), cam.direction, _quat

# warning : `cam.position =` is mandatory, or nothing will move
cam.position = vec3.transformQuat vec3.create(), cam.position, _quat

This makes me sad. :(

Now I'm on the verge of officially releasing 3.0.0, and this raises significant design questions. Suddenly I'm hesitant to release this API, as it obviously encourages bad habits (temporary variables, no way to directly vec3.transform cam.direction).

It's best to get compatibility-breaking API changes, if there are to be any, out of the way here and now, before a major-version release. I'd hate to get halfway to 3.1 and realize we have to make significant API changes that would make the 3.1 API incompatible with 3.0. That's why I'm OK with holding off on a release of 3.0 until we get this thing squared away.

I've been mulling this over for some time now, and still haven't reached a decision. The only thing I've come to thus far is: totally rewrite Jax.Camera.

Rewriting Jax.Camera would not be as arduous a task as it implies. I have not decided to do this yet, but hypothetically, if I rewrite Jax.Camera, what should be added/changed?

My thoughts right now:

  1. It should be easy to initialize Jax.Camera from a quaternion. It should be equally easy to replace its current state with a quaternion (cam.rotation = [...]). Finally, it should expose rotateQuat (or a similarly-named method) so that it can be rotated by a quaternion. This also applies to Euler angles, angle/axis rotation (which is basically what is exposed right now), and 4x4 and 3x3 matrices.
  2. Don't sacrifice good coding practices for theoretical intuitiveness. If vec3.scale cam.position, [1,1,1], 2 wouldn't work in practice, it should not be possible within the API to try. If this means pulling out properties such as cam.position and replacing them with functions such as cam.setPosition, then so be it.
  3. Nice to have: something like cam.animate, analogous to jQuery's $.animate. Basically the camera should be able to transition smoothly from one state to another, so that the developer doesn't have to deal with such implementation details.

    Ponder This

How have your experiences with Jax.Camera been to date? Can you think of anything that should be addressed? What do you think about the above changes?

Goutte commented 11 years ago

Hello !

Sorry for the long delay, I was knee-deep in maths these past days and forgot a bit about jax. I also don't get email notifications when an issue is added, even though I'm watching the repo. Weird.

I found the @define voodoo a bit puzzling ; I agree that not being able to mutate camera.direction or camera.position calls for bad practice and yells one or two WTF. I did not expose this behavior in tests because I was not sure I was using it the right way.

I really like the Jax.Camera API, all in all ; roll, pitch, yaw, everything I need is there.

  1. camera.direction and camera.rotation would need integrity checks. Slowing things ? rotateQuat is a good idea.
  2. This is sad. I have no idea why mutating is not working. If it cannot be made to work, I sadly agree : setPosition is the way to go.
  3. Absolutely ! I was thinking about this too. I'm a big fan of Mootools' FX. I saw many people using tween.js, though. It's a big subject.
Goutte commented 11 years ago

Also, I saw the other day that you refactored controls. I hacked a trackball camera by coupling its direction and position. The camera keeps pointing to a model's position while moving in a constrained sphere around the model.

Making such controls super-easy for new devs wanting to hack a quick demo would be nice.

Also, multiple ~states for the Camera (looking at menus, going back looking at a model, zooming on a part of a model, going back, etc.)

This is not really the Camera's job, though, as it involves controls. And it would need a really flexible animate. (i like the name, but don't like jquery's function signature, it's not jaxy)

Also, most of the easings are already well-coded, which is nice.

sinisterchipmunk commented 11 years ago

As long as we're visiting this, I'd like to see events play a more significant role throughout the API -- as I've mentioned before in other discussions.

So, have you any thoughts about this kind of general usage (putting aside animations and transitions and whatnot for the time being):

_movementDirection = vec3.fromValues 0, 1, 0

index: ->
  tmp = vec3.create()
  @activeCamera.on 'change:position', =>
    vec3.subtract tmp, @model.camera.get('position'), @activeCamera.get('position')
    @activeCamera.set 'direction', tmp

update: (tc) ->
  pos = @activeCamera.get 'position'
  vec3.add pos, pos, _movementDirection
  @activeCamera.set 'position', pos

This would produce a camera that is moving along a vector [0, 1, 0] and every time it moves, it triggers a change:position event. This event is caught by the code in index so that the active camera is always "looking at" some model's current position.

I've stolen some (all?) of the syntax from Backbone.js, though I am open to other suggestions as well. The general idea is that you could listen for "change" to be notified of any change on the camera, or "change:property" to be notified of changes to a specific property.

One caveat is that you can still get away with the original issue, like so:

pos = @activeCamera.get 'position'
vec3.add pos, pos, _movementDirection
# the camera has not actually moved yet

However, the same could be accomplished with getPosition and setPosition et al, so at some point I suppose we just have to rely on documentation. I feel that this approach is, at least, a little more intuitive than the property assignments that were brought to us by @define.

One really nice benefit to be gained from this approach is that properties could be defined willy-nilly by anyone on anything. For example, there's nothing to prevent me from listening for change:highScore. Jax won't be assigning any value to highScore on its own, of course, but subsequently setting that property's value in your own code would work as expected.

It also sort of normalizes the setters and getters APIs, allowing Jax to listen to relevant events internally to decide when the time is right to re-calculate the various matrices and whatnot.

Goutte commented 11 years ago

I tried the Jax.EventEmitter mixin. I don't know how much events impact perfs, but I like the idea of managing the camera's automatic behaviors as listeners. It's more... tidy. It will help a lot to create super-smart cameras.

I'm a tad wary of the #get 'position' in favor of #getPosition, as explicit works better with IDEs. On the other hand, I really like the change:highScore benefit ! And it's no good relying too much on IDEs.

Another benefit of the set approach will be for animations, I suppose.


I hacked together a small webgl game using Jax, btw.

sinisterchipmunk commented 11 years ago

I've rewritten Jax.Camera according to this discussion. It uses get('property') to return a property value, but the built-in ones are documented as read-only, and modifying them in-place is not recommended. (Custom properties can be set and gotten as normal.) For now I've also implemented other methods for manipulation, e.g. setPosition. Over the next few days I'm considering whether to replace these with an overridden set('position', ...) method. Still on the fence.

https://github.com/sinisterchipmunk/jax/blob/master/jax-core/lib/assets/javascripts/jax/camera.js.coffee

Performance is good, about on par with the previous implementation but with less overall confusion in the API (I hope).

Also implemented the animate method. I think it still has some small bugs, which will shake loose through usage, but I'm pretty impressed with the initial result. I'm using tween.js because its low-level approach allows me to animate only on specific, custom-defined fields, and not waste cycles animating vectors we don't need.

This allows us to expose some very powerful options. Tween's easing and interpolation functions can also be set as options, and the animation:start, animation:update and animation:complete events all fire as expected.

Coolest thing about the animations is, since pretty much every object has a camera, every object can be animated just as easily as the active camera -- and the events should make it easy for the model to react, such as by starting a "Walking" animation when its camera starts moving and returning to a "Standing" animation when its camera stops moving.