Fix rotation, change matrix update implementation

This commit is contained in:
Peaceultime 2024-06-27 17:03:00 +02:00
parent 6f7a8cc3d2
commit 9cf5ccaf81
3 changed files with 76 additions and 140 deletions

View File

@ -4,18 +4,13 @@ import { AABB } from '../physics/common';
import Quadtree from '../physics/quadtree.class'; import Quadtree from '../physics/quadtree.class';
import { FreeList, clamp } from '../common'; import { FreeList, clamp } from '../common';
const _position = new Three.Vector3(); const _mat = new Three.Matrix4();
const _rotation = new Three.Quaternion();
const _euler = new Three.Euler();
const _scale = new Three.Vector3();
export default class Asset export default class Asset
{ {
mat: Three.Matrix4;
layer: number;
selected: boolean = false; selected: boolean = false;
//@ts-expect-error #layer: number;
#aabb: AABB; #aabb: AABB;
#posX: number; #posX: number;
@ -23,8 +18,9 @@ export default class Asset
#rot: number; #rot: number;
#scaleX: number; #scaleX: number;
#scaleY: number; #scaleY: number;
#shearX: number;
#shearY: number; #sin: number;
#cos: number;
#index: number; #index: number;
#quad?: number; #quad?: number;
@ -33,29 +29,38 @@ export default class Asset
static quadtree: Quadtree = new Quadtree({ x1: -CONST.RESOLUTION_X / 2, x2: CONST.RESOLUTION_X / 2, y1: -CONST.RESOLUTION_Y / 2, y2: CONST.RESOLUTION_Y / 2 }, 6, 10); static quadtree: Quadtree = new Quadtree({ x1: -CONST.RESOLUTION_X / 2, x2: CONST.RESOLUTION_X / 2, y1: -CONST.RESOLUTION_Y / 2, y2: CONST.RESOLUTION_Y / 2 }, 6, 10);
static assets: FreeList<Asset> = new FreeList<Asset>(); static assets: FreeList<Asset> = new FreeList<Asset>();
constructor(mat?: Three.Matrix4, layer?: number) constructor(posX?: number, posY?: number, scaleX?: number, scaleY?: number, rotation?: number, layer?: number)
{ {
//TODO: Remake the value computation to include shear determination this.#posX = posX ?? 0, this.#posY = posY ?? 0, this.#scaleX = scaleX ?? 0, this.#scaleY = scaleY ?? 0;
this.mat = mat ?? new Three.Matrix4(); this.#rot = rotation ?? 0, this.#cos = Math.cos(this.#rot), this.#sin = Math.sin(this.#rot);
this.mat.decompose(_position, _rotation, _scale); this.#layer = layer ?? 0;
this.#posX = _position.x;
this.#posY = _position.y;
this.#rot = _euler.setFromQuaternion(_rotation).z;
this.#scaleX = _scale.x;
this.#scaleY = _scale.y;
this.#shearX = 0;
this.#shearY = 0;
this.#updateAABB();
this.layer = layer ?? 0;
this.#index = Asset.assets.insert(this); this.#index = Asset.assets.insert(this);
console.log(this);
Asset.instance.instanceMatrix.setComponent(this.#index, 0, this.#cos * this.#scaleX);
Asset.instance.instanceMatrix.setComponent(this.#index, 1, this.#sin * this.#scaleX);
Asset.instance.instanceMatrix.setComponent(this.#index, 4, -this.#sin * this.#scaleY);
Asset.instance.instanceMatrix.setComponent(this.#index, 5, this.#cos * this.#scaleY);
Asset.instance.instanceMatrix.setComponent(this.#index, 12, this.#posX);
Asset.instance.instanceMatrix.setComponent(this.#index, 13, this.#posY);
Asset.instance.instanceMatrix.addUpdateRange(this.#index * 16, 16);
Asset.instance.instanceMatrix.needsUpdate = true;
this.#aabb = {
x1: -(this.#scaleX * Math.abs(this.#cos) + this.#scaleY * Math.abs(this.#sin)) / 2 + this.#posX,
x2: (this.#scaleX * Math.abs(this.#cos) + this.#scaleY * Math.abs(this.#sin)) / 2 + this.#posX,
y1: -(this.#scaleX * Math.abs(this.#sin) + this.#scaleY * Math.abs(this.#cos)) / 2 + this.#posY,
y2: (this.#scaleX * Math.abs(this.#sin) + this.#scaleY * Math.abs(this.#cos)) / 2 + this.#posY,
}
} }
get aabb(): AABB { get aabb(): AABB {
return this.#aabb; return this.#aabb;
} }
get layer(): number {
return this.#layer;
}
get posX(): number { get posX(): number {
return this.#posX; return this.#posX;
} }
@ -71,27 +76,6 @@ export default class Asset
get scaleY(): number { get scaleY(): number {
return this.#scaleY; return this.#scaleY;
} }
#updateAABB(): void {
const aabb = { x1: -0.5, x2: 0.5, y1: -0.5, y2: 0.5 };
const e = this.mat.elements;
const x1 = aabb.x1 * e[0] + aabb.y1 * e[4] + e[12];
const x2 = aabb.x2 * e[0] + aabb.y2 * e[4] + e[12];
const y1 = aabb.x1 * e[1] + aabb.y1 * e[5] + e[13];
const y2 = aabb.x2 * e[1] + aabb.y2 * e[5] + e[13];
const x3 = aabb.x2 * e[0] + aabb.y1 * e[4] + e[12];
const x4 = aabb.x1 * e[0] + aabb.y2 * e[4] + e[12];
const y3 = aabb.x2 * e[1] + aabb.y1 * e[5] + e[13];
const y4 = aabb.x1 * e[1] + aabb.y2 * e[5] + e[13];
this.#aabb = {
x1: Math.min(x1, x2, x3, x4),
x2: Math.max(x1, x2, x3, x4),
y1: Math.min(y1, y2, y3, y4),
y2: Math.max(y1, y2, y3, y4)
};
}
insert(): Asset insert(): Asset
{ {
this.#quad = Asset.quadtree.insert(this.#index, this.#aabb); this.#quad = Asset.quadtree.insert(this.#index, this.#aabb);
@ -102,18 +86,17 @@ export default class Asset
{ {
this.#quad !== undefined && Asset.quadtree.remove(this.#quad); this.#quad !== undefined && Asset.quadtree.remove(this.#quad);
Asset.assets.erase(this.#index); Asset.assets.erase(this.#index);
Asset.instance.setMatrixAt(this.#index, this.mat.identity()); Asset.instance.setMatrixAt(this.#index, _mat);
Asset.instance.instanceMatrix.addUpdateRange(this.#index * 16, 16);
Asset.instance.instanceMatrix.needsUpdate = true; Asset.instance.instanceMatrix.needsUpdate = true;
return this; return this;
} }
update(quad: boolean = true): Asset update(updateQuad: boolean = true): Asset
{ {
this.#updateAABB();
Asset.instance.setMatrixAt(this.#index, this.mat);
Asset.instance.instanceMatrix.needsUpdate = true; Asset.instance.instanceMatrix.needsUpdate = true;
if(quad) if(updateQuad)
{ {
this.#quad !== undefined && Asset.quadtree.remove(this.#quad); this.#quad !== undefined && Asset.quadtree.remove(this.#quad);
this.#quad = Asset.quadtree.insert(this.#index, this.#aabb); this.#quad = Asset.quadtree.insert(this.#index, this.#aabb);
@ -123,83 +106,51 @@ export default class Asset
} }
moveTo(x: number, y: number): Asset moveTo(x: number, y: number): Asset
{ {
const e = this.mat.elements; this.#aabb.x1 -= this.#posX - x;
this.#aabb.x2 -= this.#posX - x;
this.#aabb.y1 -= this.#posY - y;
this.#aabb.y2 -= this.#posY - y;
e[12] = this.#posX = x; this.#posX = x, this.#posY = y;
e[13] = this.#posY = y;
Asset.instance.instanceMatrix.setComponent(this.#index, 12, x);
Asset.instance.instanceMatrix.setComponent(this.#index, 13, y);
return this; return this;
} }
rotateTo(rad: number): Asset rotateTo(rad: number): Asset
{ {
this.#rot = rad % (Math.PI * 2); this.#rot = (rad + Math.PI) % (2*Math.PI) - Math.PI, this.#cos = Math.cos(this.#rot), this.#sin = Math.sin(this.#rot);
const e = this.mat.elements; Asset.instance.instanceMatrix.setComponent(this.#index, 0, this.#cos * this.#scaleX);
const cos = Math.cos(rad), sin = Math.sin(rad), tanX = Math.tan(this.#shearX), tanY = Math.tan(this.#shearY); Asset.instance.instanceMatrix.setComponent(this.#index, 1, this.#sin * this.#scaleX);
Asset.instance.instanceMatrix.setComponent(this.#index, 4, -this.#sin * this.#scaleY);
Asset.instance.instanceMatrix.setComponent(this.#index, 5, this.#cos * this.#scaleY);
e[0] = cos * this.#scaleX + -sin * this.#shearY; this.#aabb.x1 = -(this.#scaleX * Math.abs(this.#cos) + this.#scaleY * Math.abs(this.#sin)) / 2 + this.#posX;
e[1] = sin * this.#scaleX + cos * this.#shearX; this.#aabb.x2 = (this.#scaleX * Math.abs(this.#cos) + this.#scaleY * Math.abs(this.#sin)) / 2 + this.#posX;
e[4] = cos * this.#shearX + -sin * this.#scaleY; this.#aabb.y1 = -(this.#scaleX * Math.abs(this.#sin) + this.#scaleY * Math.abs(this.#cos)) / 2 + this.#posY;
e[5] = sin * this.#shearX + cos * this.#scaleY; this.#aabb.y2 = (this.#scaleX * Math.abs(this.#sin) + this.#scaleY * Math.abs(this.#cos)) / 2 + this.#posY;
return this;
}
scaleTo(x: number, y: number): Asset
{
x = clamp(x, 0.1, CONST.RESOLUTION_X);
y = clamp(y, 0.1, CONST.RESOLUTION_Y);
this.#scaleX = x;
this.#scaleY = y;
const e = this.mat.elements;
const cos = Math.cos(this.#rot), sin = Math.sin(this.#rot);
e[0] = cos * this.#scaleX + -sin * this.#shearY;
e[1] = sin * this.#scaleX + cos * this.#shearX;
e[4] = cos * this.#shearX + -sin * this.#scaleY;
e[5] = sin * this.#shearX + cos * this.#scaleY;
return this;
}
shearTo(x: number, y: number): Asset
{
x = clamp(x, 0.1, CONST.RESOLUTION_X);
y = clamp(y, 0.1, CONST.RESOLUTION_Y);
this.#shearX = x;
this.#shearY = y;
const e = this.mat.elements;
const cos = Math.cos(this.#rot), sin = Math.sin(this.#rot);
e[0] = cos * this.#scaleX + -sin * this.#shearY;
e[1] = sin * this.#scaleX + cos * this.#shearX;
e[4] = cos * this.#shearX + -sin * this.#scaleY;
e[5] = sin * this.#shearX + cos * this.#scaleY;
return this; return this;
} }
matchAABB(aabb: AABB): Asset matchAABB(aabb: AABB): Asset
{ {
const oldAABB = this.#aabb;
this.#aabb = aabb; this.#aabb = aabb;
this.#scaleX = aabb.x2 - aabb.x1; this.#posX = this.#aabb.x1 + (this.#aabb.x2 - this.#aabb.x1) / 2, this.#posY = this.#aabb.y1 + (this.#aabb.y2 - this.#aabb.y1) / 2;
this.#scaleY = aabb.y2 - aabb.y1; this.#scaleX *= (this.#aabb.x2 - this.#aabb.x1) / (oldAABB.x2 - oldAABB.x1), this.#scaleY *= (this.#aabb.y2 - this.#aabb.y1) / (oldAABB.y2 - oldAABB.y1);
this.#posX = aabb.x1 + this.#scaleX / 2; console.log(this.#posX, this.#posY, this.#scaleX, this.#scaleY);
this.#posY = aabb.y1 + this.#scaleY / 2;
const e = this.mat.elements; Asset.instance.instanceMatrix.setComponent(this.#index, 0, this.#cos * this.#scaleX);
const cos = Math.cos(this.#rot), sin = Math.sin(this.#rot); Asset.instance.instanceMatrix.setComponent(this.#index, 1, this.#sin * this.#scaleX);
Asset.instance.instanceMatrix.setComponent(this.#index, 4, -this.#sin * this.#scaleY);
e[0] = cos * this.#scaleX + -sin * this.#shearY; Asset.instance.instanceMatrix.setComponent(this.#index, 5, this.#cos * this.#scaleY);
e[1] = sin * this.#scaleX + cos * this.#shearX; Asset.instance.instanceMatrix.setComponent(this.#index, 12, this.#posX);
e[4] = cos * this.#shearX + -sin * this.#scaleY; Asset.instance.instanceMatrix.setComponent(this.#index, 13, this.#posY);
e[5] = sin * this.#shearX + cos * this.#scaleY;
e[12] = this.#posX;
e[13] = this.#posY;
return this; return this;
} }

View File

@ -18,23 +18,12 @@ Selector.init();
const r = new Random(0); const r = new Random(0);
let dragMode = DragMode.pan, dragFrom: number | undefined; let dragMode = DragMode.pan, dragFrom: number | undefined;
/*for(let i = 0; i < 10; i++) new Asset(0, 0, 20, 10, -Math.PI / 4).update();
{
new Asset(new THREE.Matrix4(), 1).rotateTo(r.nextFloat(Math.PI * 2))
.scaleTo(r.nextFloat(10, 30), r.nextFloat(10, 30))
.moveTo(r.nextInt(-0.5 * RESOLUTION_X, 0.5 * RESOLUTION_X), r.nextInt(-0.5 * RESOLUTION_Y, 0.5 * RESOLUTION_Y))
.update();
}*/
window.asset = new Asset().moveTo(0, 0).rotateTo(Math.PI / 3).scaleTo(20, 20).update();
new Asset().moveTo(40, 0).rotateTo(0).scaleTo(20, 20).update();
new Asset().moveTo(-40, 0).rotateTo(0).scaleTo(20, 20).update();
window.Selector = Selector; window.Selector = Selector;
Asset.instance.count = Asset.assets.length; Asset.instance.count = Asset.assets.length;
Asset.instance.frustumCulled = false;
Asset.instance.computeBoundingBox();
Asset.instance.computeBoundingSphere();
Renderer.scene.add(Asset.instance); Renderer.scene.add(Asset.instance);
@ -63,9 +52,7 @@ Input.onDragStart((s, button) => {
Input.onDragEnd((s, e, _) => { Input.onDragEnd((s, e, _) => {
if(dragMode === DragMode.select) if(dragMode === DragMode.select)
{ {
const n = performance.now();
const selection = Asset.quadtree.query({x1: Math.min(s.x, e.x), x2: Math.max(s.x, e.x), y1: Math.min(s.y, e.y), y2: Math.max(s.y, e.y)}).map(e => Asset.assets.get(e)); const selection = Asset.quadtree.query({x1: Math.min(s.x, e.x), x2: Math.max(s.x, e.x), y1: Math.min(s.y, e.y), y2: Math.max(s.y, e.y)}).map(e => Asset.assets.get(e));
console.log("Fetching %s out of %s elements in %sms", selection.length, Asset.assets.length, performance.now() - n);
if(Input.keys['shift']) Selector.toggle(selection); if(Input.keys['shift']) Selector.toggle(selection);
else Selector.select(selection); else Selector.select(selection);
@ -86,7 +73,7 @@ Input.onDrag((delta, start, end, _) => {
} }
else if (dragMode === DragMode.rotate && Selector.selected) else if (dragMode === DragMode.rotate && Selector.selected)
{ {
Selector.rotate(delta.x, delta.y); Selector.rotate(end.x - delta.x, end.y - delta.y, end.x, end.y);
} }
else if (dragMode === DragMode.scale && Selector.selected) else if (dragMode === DragMode.scale && Selector.selected)
{ {

View File

@ -238,10 +238,7 @@ export default class Selector
static move(x: number, y: number): void static move(x: number, y: number): void
{ {
Selector.selection.forEach(e => e.moveTo(e.posX + x, e.posY + y).update(false)); Selector.selection.forEach(e => e.moveTo(e.posX + x, e.posY + y).update(false));
Asset.instance.computeBoundingBox();
Asset.instance.computeBoundingSphere();
Selector.#selectionMesh.box.translate(_vector.set(x, y, 0)); Selector.#selectionMesh.box.translate(_vector.set(x, y, 0));
Selector.#ghostMesh.box.translate(_vector.set(x, y, 0)); Selector.#ghostMesh.box.translate(_vector.set(x, y, 0));
@ -250,14 +247,18 @@ export default class Selector
Selector.gizmoScale[2].position.add({ x: x, y: y, z: 0 }); Selector.gizmoScale[2].position.add({ x: x, y: y, z: 0 });
Selector.gizmoScale[3].position.add({ x: x, y: y, z: 0 }); Selector.gizmoScale[3].position.add({ x: x, y: y, z: 0 });
} }
static rotate(x: number, y: number): void static rotate(startX: number, startY: number, endX: number, endY: number): void
{ {
_vector.set(x, y, 0).normalize() const centerX = Selector.gizmoScale[0].position.x + (Selector.gizmoScale[3].position.x - Selector.gizmoScale[0].position.x) / 2,
const rad = Math.atan2(-_vector.y, _vector.x); centerY = Selector.gizmoScale[0].position.y + (Selector.gizmoScale[3].position.y - Selector.gizmoScale[0].position.y) / 2;
Selector.selection.forEach(e => e.rotateTo(e.rot + rad).update(false));
Asset.instance.computeBoundingBox(); startX -= centerX;
Asset.instance.computeBoundingSphere(); startY -= centerY;
endX -= centerX;
endY -= centerY;
const rad = Math.atan2(startX*endY-startY*endX,startX*endX+startY*endY);
Selector.selection.forEach(e => e.rotateTo(e.rot + rad).update(false));
} }
static scale(dragFrom: number, x: number, y: number): void static scale(dragFrom: number, x: number, y: number): void
{ {
@ -274,9 +275,6 @@ export default class Selector
e.matchAABB(aabb).update(false); e.matchAABB(aabb).update(false);
}); });
Asset.instance.computeBoundingBox();
Asset.instance.computeBoundingSphere();
Selector.#selectionMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]); Selector.#selectionMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]);
Selector.#ghostMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]); Selector.#ghostMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]);
} }