diff --git a/package.json b/package.json index 83717eb..f3815ab 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "bunx --bun vite", + "dev": "bunx --bun vite --force", "build": "tsc && vite build", "preview": "vite preview" }, diff --git a/src/assets/asset.class.ts b/src/assets/asset.class.ts index 02e2269..2e7b322 100644 --- a/src/assets/asset.class.ts +++ b/src/assets/asset.class.ts @@ -2,7 +2,7 @@ import * as Three from 'three'; import * as CONST from '../consts'; import { AABB } from '../physics/common'; import Quadtree from '../physics/quadtree.class'; -import { FreeList } from '../common'; +import { FreeList, clamp } from '../common'; const _position = new Three.Vector3(); const _rotation = new Three.Quaternion(); @@ -23,6 +23,8 @@ export default class Asset #rot: number; #scaleX: number; #scaleY: number; + #shearX: number; + #shearY: number; #index: number; #quad?: number; @@ -33,14 +35,18 @@ export default class Asset constructor(mat?: Three.Matrix4, layer?: number) { + //TODO: Remake the value computation to include shear determination this.mat = mat ?? new Three.Matrix4(); this.mat.decompose(_position, _rotation, _scale); this.#posX = _position.x; this.#posY = _position.y; this.#rot = _euler.setFromQuaternion(_rotation).z; - this.#scaleX = _position.x; - this.#scaleY = _position.y; + this.#scaleX = _scale.x; + this.#scaleY = _scale.y; + + this.#shearX = 1; + this.#shearY = 1; this.#updateAABB(); this.layer = layer ?? 0; @@ -101,7 +107,7 @@ export default class Asset return this; } - update(quad: boolean = true): void + update(quad: boolean = true): Asset { this.#updateAABB(); Asset.instance.setMatrixAt(this.#index, this.mat); @@ -112,6 +118,8 @@ export default class Asset this.#quad !== undefined && Asset.quadtree.remove(this.#quad); this.#quad = Asset.quadtree.insert(this.#index, this.#aabb); } + + return this; } moveTo(x: number, y: number): Asset { @@ -124,10 +132,10 @@ export default class Asset } rotateTo(rad: number): Asset { - this.#rot = rad; + this.#rot = rad % (Math.PI * 2); const e = this.mat.elements; - const cos = Math.cos(rad), sin = Math.sin(rad); + const cos = Math.cos(rad), sin = Math.sin(rad), tanX = Math.tan(this.#shearX), tanY = Math.tan(this.#shearY); e[0] = cos * this.#scaleX; e[1] = -sin * this.#scaleX; @@ -138,6 +146,9 @@ export default class Asset } 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; @@ -145,10 +156,33 @@ export default class Asset const cos = Math.cos(this.#rot), sin = Math.sin(this.#rot); e[0] = cos * this.#scaleX; - e[1] = -sin * this.#scaleX; - e[4] = sin * this.#scaleY; + e[1] = -sin * this.#shearX; + e[4] = sin * this.#shearY; e[5] = cos * this.#scaleY; + return this; + } + matchAABB(aabb: AABB): Asset + { + this.#aabb = aabb; + + this.#scaleX = aabb.x2 - aabb.x1; + this.#scaleY = aabb.y2 - aabb.y1; + + this.#posX = aabb.x1 + this.#scaleX / 2; + this.#posY = aabb.y1 + this.#scaleY / 2; + + const e = this.mat.elements; + const cos = Math.cos(this.#rot), sin = Math.sin(this.#rot); + + e[0] = cos * this.#scaleX; + e[1] = -sin * this.#shearX; + e[4] = sin * this.#shearY; + e[5] = cos * this.#scaleY; + + e[12] = this.#posX; + e[13] = this.#posY; + return this; } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 328e58c..fd812df 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ -import * as THREE from 'three'; +//import * as THREE from 'three'; import Renderer from './renderer/renderer.class'; import Asset from './assets/asset.class'; -import { RESOLUTION_X, RESOLUTION_Y } from './consts'; +//import { RESOLUTION_X, RESOLUTION_Y } from './consts'; import Input from './renderer/input.class'; import { Random, clamp } from './common'; import Selector from './renderer/selector.class'; @@ -18,13 +18,18 @@ Selector.init(); const r = new Random(0); let dragMode = DragMode.pan, dragFrom: number | undefined; -for(let i = 0; i < 10; i++) +/*for(let i = 0; i < 10; i++) { 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; Asset.instance.count = Asset.assets.length; @@ -79,6 +84,10 @@ Input.onDrag((delta, start, end, _) => { { Selector.move(delta.x, delta.y); } + else if (dragMode === DragMode.rotate && Selector.selected) + { + Selector.rotate(delta.x, delta.y); + } else if (dragMode === DragMode.scale && Selector.selected) { Selector.scale(dragFrom!, delta.x, delta.y); diff --git a/src/renderer/renderer.class.ts b/src/renderer/renderer.class.ts index 0597fb5..eae8093 100644 --- a/src/renderer/renderer.class.ts +++ b/src/renderer/renderer.class.ts @@ -14,6 +14,8 @@ export enum CursorType leftright = "ew-resize", nesw = "nesw-resize", nwse = "nwse-resize", + //rotate = "url(public/rotate.png)" + rotate = "crosshair" //tmp } export default class Renderer { @@ -38,7 +40,7 @@ export default class Renderer this.camera = new Three.OrthographicCamera(); this.camera.position.z = 500; - this.#zoom = 1; + this.#zoom = 5; const stats = this.#stats = new Stats(); stats.showPanel(0); diff --git a/src/renderer/selector.class.ts b/src/renderer/selector.class.ts index ad99e29..5529a81 100644 --- a/src/renderer/selector.class.ts +++ b/src/renderer/selector.class.ts @@ -1,6 +1,6 @@ import * as Three from "three"; import Asset from "../assets/asset.class"; -import { Point, intersects, intersectsAsset, sqrtLen } from "../physics/common"; +import { Point, intersects, sqrtLen } from "../physics/common"; import Renderer, { CursorType } from "./renderer.class"; import { QUAD } from '../consts'; import { DragMode } from "../main"; @@ -8,10 +8,10 @@ import { DragMode } from "../main"; const _vector = new Three.Vector3(); const orientation = { - 0: { x: -1, y: -1 }, - 1: { x: -1, y: 1 }, - 2: { x: -1, y: 1 }, - 3: { x: -1, y: 1 }, + 0: {neighborX: 2, neighborY: 1, opposite: 3}, + 1: {neighborX: 3, neighborY: 0, opposite: 2}, + 2: {neighborX: 0, neighborY: 3, opposite: 1}, + 3: {neighborX: 1, neighborY: 2, opposite: 0} }; export default class Selector @@ -197,6 +197,10 @@ export default class Selector minY = Selector.#selectionMesh.box.min.y, maxX = Selector.#selectionMesh.box.max.x, maxY = Selector.#selectionMesh.box.max.y; + + const centerX = minX + (maxX - minX) / 2, + centerY = minY + (maxY - minY) / 2, + centerLen = sqrtLen(x, y, centerX, centerY); if (sqrtLen(x, y, Selector.gizmoScale[0].position.x, Selector.gizmoScale[0].position.y) <= 500 / Renderer.zoom) { @@ -223,6 +227,11 @@ export default class Selector Renderer.cursor(CursorType.move); return { mode: DragMode.move }; } + else if (centerLen <= sqrtLen(centerX, centerY, maxX + (maxX - minX) / 2, centerY)) + { + Renderer.cursor(CursorType.rotate); + return { mode: DragMode.rotate }; + } return { mode: DragMode.select }; } @@ -241,25 +250,68 @@ export default class Selector Selector.gizmoScale[2].position.add({ x: x, y: y, z: 0 }); Selector.gizmoScale[3].position.add({ x: x, y: y, z: 0 }); } - static scale(dragFrom: number, x: number, y: number): void + static rotate(x: number, y: number): void { - console.log(dragFrom, x, y); - - //@ts-ignore - x *= orientation[dragFrom].x; - //@ts-ignore - y *= orientation[dragFrom].y; - - Selector.selection.forEach(e => e.scaleTo(e.scaleX + x, e.scaleY + y).moveTo(e.posX - x / 2, e.posY - y / 2).update(false)); - - Selector.#selectionMesh.box.translate(_vector.set(-x / 2, -y / 2, 0)); - - Selector.gizmoScale[0].position.add({ x: -x / 2, y: -y / 2, z: 0 }); - Selector.gizmoScale[1].position.add({ x: -x / 2, y: -y / 2, z: 0 }); - Selector.gizmoScale[2].position.add({ x: -x / 2, y: -y / 2, z: 0 }); - Selector.gizmoScale[3].position.add({ x: -x / 2, y: -y / 2, z: 0 }); + _vector.set(x, y, 0).normalize() + const rad = Math.atan2(-_vector.y, _vector.x); + Selector.selection.forEach(e => e.rotateTo(e.rot + rad).update(false)); Asset.instance.computeBoundingBox(); Asset.instance.computeBoundingSphere(); } -} \ No newline at end of file + static scale(dragFrom: number, x: number, y: number): void + { + //@ts-ignore + const o = orientation[dragFrom]; + + Selector.gizmoScale[dragFrom].position.add({ x: x, y: y, z: 0 }); + Selector.gizmoScale[o.neighborX].position.add({ x: x, y: 0, z: 0 }); + Selector.gizmoScale[o.neighborY].position.add({ x: 0, y: y, z: 0 }); + + Selector.selection.forEach(e => { + //DEBUG ONLY, IT IS NOT CORRECT + const aabb = { x1: Selector.gizmoScale[0].position.x, y1: Selector.gizmoScale[0].position.y, x2: Selector.gizmoScale[3].position.x, y2: Selector.gizmoScale[3].position.y } + e.matchAABB(aabb).update(false); + }); + + Asset.instance.computeBoundingBox(); + Asset.instance.computeBoundingSphere(); + + Selector.#selectionMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]); + Selector.#ghostMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]); + } +} + +/* + + + //@ts-ignore + const o = orientation[dragFrom]; + + const sizeX = Selector.gizmoScale[3].position.x - Selector.gizmoScale[0].position.x, + sizeY = Selector.gizmoScale[3].position.y - Selector.gizmoScale[0].position.y; + + const centerX = Selector.gizmoScale[0].position.x + sizeX / 2, + centerY = Selector.gizmoScale[0].position.y + sizeY / 2; + + Selector.selection.forEach(e => { + const scaleX = (e.aabb.x2 - e.aabb.x1) / sizeX, scaleY = (e.aabb.y2 - e.aabb.y1) / sizeY; + const moveX = (e.posX - centerX) / sizeX, moveY = (e.posY - centerY) / sizeY; + + const cos = Math.abs(Math.cos(e.rot)), sin = Math.abs(Math.sin(e.rot)); + const localX = x * cos + y * -sin, localY = x * -sin + y * cos; + + e.scaleTo(e.scaleX + localX * scaleX, e.scaleY + localY * scaleY).moveTo(e.posX + x / 2 + x * moveX, e.posY + y / 2 + y * moveY).update(false) + }); + + Asset.instance.computeBoundingBox(); + Asset.instance.computeBoundingSphere(); + + Selector.gizmoScale[dragFrom].position.add({ x: x, y: y, z: 0 }); + Selector.gizmoScale[o.neighborX].position.add({ x: x, y: 0, z: 0 }); + Selector.gizmoScale[o.neighborY].position.add({ x: 0, y: y, z: 0 }); + + Selector.#selectionMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]); + Selector.#ghostMesh.box.setFromPoints([Selector.gizmoScale[0].position, Selector.gizmoScale[3].position]); + +*/ \ No newline at end of file