Scaling small objects using THREE.TransformControls - three.js

I have a simple editor for 3D models using three.js.
This is my camera:
new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.01, 10);
And this is my mesh's geometry:
new THREE.BoxGeometry(0.3, 0.3, 0.3);
I'm using THREE.TransformControls to scale the mesh.
Scaling works perfectly fine when meshes have units ~ 100, but when an object is between 0 and 1, scaling is very slow. With slow, I mean that when you drag the scale slider half the canvas size, the mesh increases or decreases only a few percentages in size. I don't have this problem with other TransformControls modes.
JSFiddle demonstrating slow scaling
I could switch to bigger units, but I'd like to know if this can be solved without having to replace all units throughout my app code.
So my question: In THREE.TransformControls, is there a way to translate dragging to scaling in a more natural way while dealing with small units?

The problem is that TransformControls scales the distance of the mouse movement by a seemingly arbitrary amount (1/50) within the onMouseMove function:
} else if (_mode == "scale") {
point.sub(offset);
point.multiply(parentScale);
if (scope.space == "local") {
if (scope.axis == "XYZ") {
scale = 1 + ((point.y) / 50);
scope.object.scale.x = oldScale.x * scale;
scope.object.scale.y = oldScale.y * scale;
scope.object.scale.z = oldScale.z * scale;
} else {
point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
if (scope.axis == "X") scope.object.scale.x = oldScale.x * (1 + point.x / 50);
if (scope.axis == "Y") scope.object.scale.y = oldScale.y * (1 + point.y / 50);
if (scope.axis == "Z") scope.object.scale.z = oldScale.z * (1 + point.z / 50);
}
}
}
This parameter (50) effectively controls the "speed" at which the object is scaled (compared to how far the mouse is dragged). You're going to need to patch TransformControls in order to change it; I got it to work better by changing 50 to something smaller, like 1.
} else if (_mode == "scale") {
point.sub(offset);
point.multiply(parentScale);
// by default this is 50; set it to smaller values if you're using smaller units
scaleSpeed = 1;
if (scope.space == "local") {
if (scope.axis == "XYZ") {
scale = 1 + ((point.y) / scaleSpeed);
scope.object.scale.x = oldScale.x * scale;
scope.object.scale.y = oldScale.y * scale;
scope.object.scale.z = oldScale.z * scale;
} else {
point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
if (scope.axis == "X") scope.object.scale.x = oldScale.x * (1 + point.x / scaleSpeed);
if (scope.axis == "Y") scope.object.scale.y = oldScale.y * (1 + point.y / scaleSpeed);
if (scope.axis == "Z") scope.object.scale.z = oldScale.z * (1 + point.z / scaleSpeed);
}
}
}
Working fiddle: http://jsfiddle.net/494uvxfg/ .
three.js r68

Related

pgraphics element won't center on mobile devices | drawingContext.drawImage causing mobile offset

I’m working on some of Tim Rodenbrökers code involving a copy() function (https://timrodenbroeker.de/processing-tutorial-kinetic-typography-1/), expanding it and making it ready for web.
This involves replacing the copy() function with drawingContext.drawImage() for performance increase (found here: https://discourse.processing.org/t/p5-js-copy-function-slow-performance-since-version-0-10-0/30007).
Doing this works great for desktop; on mobile, however, the pgraphics element (centered on the canvas, usually), moves position.
Using the regular copy() function centeres it correctly.
The positioning varies according to mobile screen size, I can’t seem to figure out the exact behavior to fix. It's not the font size, I've tried adapting the position to screen.size and document.documentElement.clientWidth, no luck.
let font;
let pg;
function setup() {
font = loadFont("./assets/FGrotesk-Regular.otf");
createCanvas(innerWidth, innerHeight);
pg = createGraphics(innerWidth, innerHeight, P2D);
frameRate(60);
pixelDensity(1);
}
function draw() {
background(0);
pg.background(0);
pg.fill(255);
pg.textFont(font);
pg.textSize(380);
pg.push();
pg.translate(innerWidth / 2, innerHeight / 2);
pg.textAlign(CENTER, CENTER);
pg.text("Enrico", 0, -50);
pg.text("Gisana", 0, 50);
pg.pop();
let tilesX = 400;
let tilesY = 20;
let tileW = int(width / tilesX);
let tileH = int(height / tilesY);
for (y = 0; y < tilesY; y++) {
for (x = 0; x < tilesX; x++) {
// WARP
let wave_x = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
let wave_y = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
if (mouseX - (width / 2) >= 0) {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
} else {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
}
if (mouseY - (height / 2) >= 0) {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
} else {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
}
// SOURCE
let sx = x * tileW + wave_x;
// + wave should be added here
let sy = y * tileH - wave_y;
let sw = tileW;
let sh = tileH;
// DESTINATION
let dx = x * tileW;
let dy = y * tileH;
let dw = tileW;
let dh = tileH;
drawingContext.drawImage(pg.elt, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
}

How to calculate an orbit around the earth with respect to Mercator projection?

I'm trying to create a small interactive UFO-flying-around-the-earth scene in three.js.
I thought it would be good to control the UFO on a 2D projection of the map (like a minimap), convert the pixel coordinates to lat/lng coordinates and finally transform lat/lng to a Vector that I can use for my 3D scene.
As it turns out, it wasn't.
Works great if the UFO flies around the Equator (+/- some degrees), but for sure I forgot to apply the projection to my 2D controls:
Now I'm a little bit lost. My controls (WASD keys) basically sets the speed and the rotation of the UFO on the minimap, but I have to add some sort of "correction" to the player. That's how the values are set at the moment:
let left = parseFloat(this.minimapPlayer.style.left) + this.speed * Math.cos(Math.PI / 180 * this.rotation)
let top = parseFloat(this.minimapPlayer.style.top) + this.speed * Math.sin(Math.PI / 180 * this.rotation)
Is there a way to create a "real" orbit, so that my UFO doesn't always fly through both poles? Or may there be a better approach to handle the orbit of the UFO (maybe without the minimap)?
Here's the full animation code so far:
animatePlane(firstRender) {
requestAnimationFrame(this.animatePlane)
// set next position
let left = parseFloat(this.minimapPlayer.style.left) + this.speed * Math.cos(Math.PI / 180 * this.rotation)
let top = parseFloat(this.minimapPlayer.style.top) + this.speed * Math.sin(Math.PI / 180 * this.rotation)
// handle border collisions
if (left < 0) {
left = this.minimapBounds.width
}
if (left > this.minimapBounds.width) {
left = 0
}
if (top < 0 || top > this.minimapBounds.height) {
this.rotation = this.rotation * -1
if (this.rotation > 0) {
this.plane.up.set(0, 1, 0)
} else {
this.plane.up.set(0, -1, 0)
}
top = top + this.speed * Math.sin(Math.PI / 180 * this.rotation)
if (left < this.minimapBounds.width / 2) {
left = left + this.speed * Math.cos(Math.PI / 180 * this.rotation) + this.minimapBounds.width / 2
} else {
left = left + this.speed * Math.cos(Math.PI / 180 * this.rotation) - this.minimapBounds.width / 2
}
}
this.minimapPlayer.style.left = `${left}px`
this.minimapPlayer.style.top = `${top}px`
// convert to lat/lng
const lat = (180 / this.minimapBounds.height) * (top - this.minimapBounds.height / 2) * -1
const lng = (360 / this.minimapBounds.width) * (left - this.minimapBounds.width / 2)
// convert to vector
const p = this.latLongToVector3(lat, lng, this.radius, 200)
this.plane.position.set(p.x, p.y, p.z)
// bottom of the plane should always look the the middle of the earth
this.plane.lookAt(new THREE.Vector3(0,0,0))
}

Moving a line geometry with three.js using GLSL

I have a line geometry made with three.js, which I want to move as a spermatozoid (i.e. the head moves first, then all of the points along the tail move accordingly to the head) using GLSL.
Here is a visual representation of what I want:
So as you can see, point G eases to (follows) H, which in turn follows point E, which in turn follows the head.
I have a working examples, being animated on the CPU. Here is the code:
class Boid {
constructor (position) {
this.position = position
this.speed = 0.0009 + Math.random() * 0.0003
this.pointsNum = 12
this.points = []
this.line = null
this.angle = Math.random() * 360
for (let i = 0; i < this.pointsNum; i += 1) {
this.points.push(new THREE.Vector3(1, 1, 1))
}
this.angle = 0
}
update (target, time) {
if (time) {
this.line.geometry.verticesNeedUpdate = true
this.line.geometry.vertices.forEach((p, i) => {
let nextP = this.line.geometry.vertices[i + 1]
if (nextP) {
// if it's not the HEAD point, follow the next point in the geometry vertices
p.x += (nextP.x - p.x) * (time * 8.0)
p.y += (nextP.y - p.y) * (time * 8.0)
p.z += (nextP.z - p.z) * (time * 8.0)
} else {
// if the point is in fact the head, ease it according to some random moving point in our scene (target)
p.x += (target.x - p.x) * time
p.y += (target.y - p.y) * time
p.z += (target.z - p.z) * time
}
})
}
}
}
And here is a working example.
This technique is working, but would like to accomplish the same stuff with GLSL. My question is how should I approach it? Should I pass the next vertex position to the previous one and ease in my vertex shader? How should I keep track of the next's point position?
Any help is more then appreciated, I have been thinking about this a lot without any success.

Formula to translate xy movement with rotation

i have a canvas element that i rotate with context.rotate();
when i drag around the image in the canvas if i rotated lets say 90 degrees, and i move to the left, the image moves down,
is there a formula in which i can apply a movement of
for example
x+5 y+2 * degrees
and i get the real movement i need to do to move the rotated canvas in the direction i want? I would like to apply it to this function which works but with the undesired efect of moving left and the image moving down `
vm.canvasMouseMove = function (event) {
vm.delta = Date.now();
if (vm.mouseisdown && vm.delta - vm.now > (1000 / 60) && (event.clientX > 0 && event.clientY > 0)) {
vm.now = vm.delta
vm.snapshot.mouse.x -= event.clientX;
vm.snapshot.offsetSlider.value -= vm.snapshot.mouse.x;
if (vm.snapshot.offsetSlider.value < -160) {
vm.snapshot.offsetSlider.value = -160
}
else if (vm.snapshot.offsetSlider.value > 160) {
vm.snapshot.offsetSlider.value = 160
}
vm.snapshot.mouse.y -= event.clientY;
vm.snapshot.verticalOffsetSlider.value += vm.snapshot.mouse.y;
if (vm.snapshot.verticalOffsetSlider.value < -120) {
vm.snapshot.verticalOffsetSlider.value = -120
}
else if (vm.snapshot.verticalOffsetSlider.value > 120) {
vm.snapshot.verticalOffsetSlider.value = 120
}
vm.snapshot.mouse.x = event.clientX;
vm.snapshot.mouse.y = event.clientY;
}
};`
This draws
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.drawImage(image, vm.snapshot.offsetSlider.value - (canvas.width / 2), (vm.snapshot.verticalOffsetSlider.value * -1) - (canvas.height / 2), vm.scaledImageW * vm.snapshot.zoomSlider.value / 100, vm.scaledImageH * vm.snapshot.zoomSlider.value / 100);
ctx.rotate(-1 * vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
vm.donePicture = canvas.toDataURL();
This made the trick, passing the offset to the translate method, then draw the image only taking into account the canvas half translation
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate((canvas.width / 2) + (vm.snapshot.offsetSlider.value), (canvas.height / 2) - vm.snapshot.verticalOffsetSlider.value);
ctx.rotate(vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.drawImage(vm.canvasImage, 0 - (canvas.width/2), 0 - (canvas.height/2), vm.scaledImageW * vm.snapshot.zoomSlider.value / 100, vm.scaledImageH * vm.snapshot.zoomSlider.value / 100);
ctx.restore();

Eliminating off-of-ball roll in Trackball controls (with code/fix)

Is the intent of the TrackballControl to have a "border" outside the trackball that induces roll? I personally dislike it. It is a bit discontinuous, and does't really have a lot of purpose (imho).
If not, the function getMouseProjectionOnBall can be changed similar to the following. This does two things (not necessarily "correctly"):
Normalize the radius to fill both axis
Map z values outside of the ball (ie where z was previously 0)
I find this a lot more natural, personally.
Thoughts?
this.getMouseProjectionOnBall = function(clientX, clientY) {
var xnormalized = (clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft) / (_this.screen.width / 2.0);
var ynormalized = (_this.screen.height * 0.5 + _this.screen.offsetTop - clientY) / (_this.screen.height / 2.0);
var mouseOnBall = new THREE.Vector3(
xnormalized,
ynormalized,
0.0
);
var length = mouseOnBall.length();
var ballRadius = 1.0; // As a fraction of the screen
if (length > ballRadius * 0.70710678118654752440) {
var temp = ballRadius / 1.41421356237309504880;
mouseOnBall.z = temp * temp / length;
// Remove old method.
// This Left z = 0, which meant rotation axis
// becomes z, which is a roll
//mouseOnBall.normalize();
} else {
mouseOnBall.z = Math.sqrt(1.0 - length * length);
}
_eye.copy(_this.object.position).sub(_this.target);
var projection = _this.object.up.clone().setLength(mouseOnBall.y);
projection.add(_this.object.up.clone().cross(_eye).setLength(mouseOnBall.x));
projection.add(_eye.setLength(mouseOnBall.z));
return projection;
};

Resources