I am ready to finally extract my Three.js labeling code out into a library. But first one or two minor tweaks.
The first is just an API adjustment. Instead of indicating the duration that a label should apply with an object literal:
new Label(earth, "Earth", {remove: 5});
new Label(mars, "Mars");
I think I prefer to simply make the third argument the optional value for the duration that the label should persist: new Label(earth, "Earth", 5);
new Label(mars, "Mars");
This is an easy enough change: function Label(object, content, duration) {
this.object = object;
this.content = content;
if (duration) this.remove(duration);
this.el = this.buildElement();
LabelPlugin.add(this);
}
The next thing that I would like to change is a bit more involved. It has to do with how the label is placed above the Three.js object. If the camera orientation is not changed, then the calculation is a (relatively) straight-forward transformation from 3D coordinates to 2D coordinates.But if the camera is rotated in any direction, then things go awry. For instance, in my solar system simulation, I include an "Earth-cam" view of Mars. To see Mars, I have to rotate the camera around the X-axis. This throws off my label placement. Specifically, the label goes in the the middle of the planet instead of above it:
To get around this, I have to multiply by a series of sines and cosines:
Label.prototype.render = function(scene, cam) {
var p3d = this.object.position.clone();
p3d.z = p3d.z + this.object.boundRadius * Math.sin(cam.rotation.x) * Math.cos(cam.rotation.y);
p3d.y = p3d.y + this.object.boundRadius * Math.cos(cam.rotation.x) * Math.cos(cam.rotation.z);
p3d.x = p3d.x - this.object.boundRadius * Math.sin(cam.rotation.z) * Math.sin(cam.rotation.y);
var projector = new THREE.Projector(),
pos = projector.projectVector(p3d, cam),
width = window.innerWidth,
height = window.innerHeight,
w = this.el.offsetWidth,
h = this.el.offsetHeight;
this.el.style.top = '' + (height/2 - height/2 * pos.y - h - 10) + 'px';
this.el.style.left = '' + (width/2 * pos.x + width/2 - w/2) + 'px';
};
The end result being that the label is again placed above the planet:I more or less figure these sines and cosines out by guessing. I know that cosine of zero is 1 and decreases and that sine of zero is 0 and increase. I use that to make educated guess of where to start with each value. For instance, the original above-the-solar system view had no rotation at all and only needed the target position to shift up in the Y direction. Given that, I knew that I needed cosines for the shift in the Y direction. But after that, I mostly play around with the cameras to figure this out by experimentation.
In the end, this seems to work for most of the cases that I tried, which ought to be good enough for the book. And if people find a case in which I got it wrong, I can always revisit.
(live code demo with the new library)
Day #678
Does your technique to add labels handle the situation of maintaining a static size i.e. so that zooming out does not shrink the text down to an illegible size? If so, I would love you to point me to a working demo (I could not get the 'live code demo' link above to work for me). Thanks!
ReplyDelete