Added rotation (but around the wrong point)
This commit is contained in:
parent
d02b487649
commit
df32b43915
34
src/main.js
34
src/main.js
|
|
@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js';
|
||||||
import { Bezier } from "/bezier.js";
|
import { Bezier } from "/bezier.js";
|
||||||
import { Quadtree } from './quadtree.js';
|
import { Quadtree } from './quadtree.js';
|
||||||
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
||||||
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp, camelToWords, generateWaveform, floodFillRegion, getShapeAtPoint, hslToRgb, drawCheckerboardBackground, hexToHsl, hsvToRgb, hexToHsv, rgbToHex, clamp, drawBorderedRect, drawCenteredText, drawHorizontallyCenteredText, deepMerge, getPointNearBox, arraysAreEqual, drawRegularPolygon, getFileExtension, createModal, deeploop } from './utils.js';
|
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp, camelToWords, generateWaveform, floodFillRegion, getShapeAtPoint, hslToRgb, drawCheckerboardBackground, hexToHsl, hsvToRgb, hexToHsv, rgbToHex, clamp, drawBorderedRect, drawCenteredText, drawHorizontallyCenteredText, deepMerge, getPointNearBox, arraysAreEqual, drawRegularPolygon, getFileExtension, createModal, deeploop, signedAngleBetweenVectors } from './utils.js';
|
||||||
import { backgroundColor, darkMode, foregroundColor, frameWidth, gutterHeight, highlight, iconSize, triangleSize, labelColor, layerHeight, layerWidth, scrubberColor, shade, shadow } from './styles.js';
|
import { backgroundColor, darkMode, foregroundColor, frameWidth, gutterHeight, highlight, iconSize, triangleSize, labelColor, layerHeight, layerWidth, scrubberColor, shade, shadow } from './styles.js';
|
||||||
import { Icon } from './icon.js';
|
import { Icon } from './icon.js';
|
||||||
const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile, readFile: readFile }= window.__TAURI__.fs;
|
const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile, readFile: readFile }= window.__TAURI__.fs;
|
||||||
|
|
@ -3735,7 +3735,7 @@ function stage() {
|
||||||
} else {
|
} else {
|
||||||
growBoundingBox(bbox, item.bbox())
|
growBoundingBox(bbox, item.bbox())
|
||||||
}
|
}
|
||||||
selection[item.idx] = {x: item.x, y: item.y, scale_x: item.scale_x, scale_y: item.scale_y}
|
selection[item.idx] = {x: item.x, y: item.y, scale_x: item.scale_x, scale_y: item.scale_y, rotation: item.rotation}
|
||||||
}
|
}
|
||||||
let transformPoint = getPointNearBox(bbox, mouse, 10)
|
let transformPoint = getPointNearBox(bbox, mouse, 10)
|
||||||
if (transformPoint) {
|
if (transformPoint) {
|
||||||
|
|
@ -3744,11 +3744,13 @@ function stage() {
|
||||||
initial: {
|
initial: {
|
||||||
x: {min: bbox.x.min, max: bbox.x.max},
|
x: {min: bbox.x.min, max: bbox.x.max},
|
||||||
y: {min: bbox.y.min, max: bbox.y.max},
|
y: {min: bbox.y.min, max: bbox.y.max},
|
||||||
|
rotation: 0,
|
||||||
selection: selection
|
selection: selection
|
||||||
},
|
},
|
||||||
current: {
|
current: {
|
||||||
x: {min: bbox.x.min, max: bbox.x.max},
|
x: {min: bbox.x.min, max: bbox.x.max},
|
||||||
y: {min: bbox.y.min, max: bbox.y.max},
|
y: {min: bbox.y.min, max: bbox.y.max},
|
||||||
|
rotation: 0,
|
||||||
selection: structuredClone(selection)
|
selection: structuredClone(selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3757,6 +3759,23 @@ function stage() {
|
||||||
transformPoint = getPointNearBox(bbox, mouse, 30, false)
|
transformPoint = getPointNearBox(bbox, mouse, 30, false)
|
||||||
if (transformPoint) {
|
if (transformPoint) {
|
||||||
stage.style.cursor = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' class='bi bi-arrow-counterclockwise' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2z'/%3E%3Cpath d='M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466'/%3E%3C/svg%3E") 12 12, auto`
|
stage.style.cursor = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' class='bi bi-arrow-counterclockwise' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2z'/%3E%3Cpath d='M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466'/%3E%3C/svg%3E") 12 12, auto`
|
||||||
|
context.dragDirection = 'r'
|
||||||
|
context.activeTransform = {
|
||||||
|
initial: {
|
||||||
|
x: {min: bbox.x.min, max: bbox.x.max},
|
||||||
|
y: {min: bbox.y.min, max: bbox.y.max},
|
||||||
|
rotation: 0,
|
||||||
|
mouse: {x: mouse.x, y: mouse.y},
|
||||||
|
selection: selection
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
x: {min: bbox.x.min, max: bbox.x.max},
|
||||||
|
y: {min: bbox.y.min, max: bbox.y.max},
|
||||||
|
rotation: 0,
|
||||||
|
mouse: {x: mouse.x, y: mouse.y},
|
||||||
|
selection: structuredClone(selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
stage.style.cursor = "default"
|
stage.style.cursor = "default"
|
||||||
}
|
}
|
||||||
|
|
@ -4088,11 +4107,21 @@ function stage() {
|
||||||
current.x.min = mouse.x
|
current.x.min = mouse.x
|
||||||
} else if (context.dragDirection.indexOf('e') != -1) {
|
} else if (context.dragDirection.indexOf('e') != -1) {
|
||||||
current.x.max = mouse.x
|
current.x.max = mouse.x
|
||||||
|
}
|
||||||
|
if (context.dragDirection == 'r') {
|
||||||
|
let pivot = {
|
||||||
|
x: (initial.x.min+initial.x.max)/2,
|
||||||
|
y: (initial.y.min+initial.y.max)/2,
|
||||||
|
}
|
||||||
|
current.rotation = signedAngleBetweenVectors(pivot, initial.mouse, mouse)
|
||||||
}
|
}
|
||||||
// Calculate the translation difference between current and initial values
|
// Calculate the translation difference between current and initial values
|
||||||
const delta_x = current.x.min - initial.x.min;
|
const delta_x = current.x.min - initial.x.min;
|
||||||
const delta_y = current.y.min - initial.y.min;
|
const delta_y = current.y.min - initial.y.min;
|
||||||
|
|
||||||
|
// This is probably unnecessary since initial rotation is 0
|
||||||
|
const delta_rot = current.rotation - initial.rotation
|
||||||
|
|
||||||
// Calculate the scaling factor based on the difference between current and initial values
|
// Calculate the scaling factor based on the difference between current and initial values
|
||||||
const scale_x_ratio = (current.x.max - current.x.min) / (initial.x.max - initial.x.min);
|
const scale_x_ratio = (current.x.max - current.x.min) / (initial.x.max - initial.x.min);
|
||||||
const scale_y_ratio = (current.y.max - current.y.min) / (initial.y.max - initial.y.min);
|
const scale_y_ratio = (current.y.max - current.y.min) / (initial.y.max - initial.y.min);
|
||||||
|
|
@ -4105,6 +4134,7 @@ function stage() {
|
||||||
item.y = initial.y.min + delta_y + yoffset * scale_y_ratio
|
item.y = initial.y.min + delta_y + yoffset * scale_y_ratio
|
||||||
item.scale_x = initialSelection[idx].scale_x * scale_x_ratio
|
item.scale_x = initialSelection[idx].scale_x * scale_x_ratio
|
||||||
item.scale_y = initialSelection[idx].scale_y * scale_y_ratio
|
item.scale_y = initialSelection[idx].scale_y * scale_y_ratio
|
||||||
|
item.rotation = initialSelection[idx].rotation + delta_rot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
35
src/utils.js
35
src/utils.js
|
|
@ -547,6 +547,40 @@ function clamp(n) {
|
||||||
return Math.min(Math.max(n,0),1)
|
return Math.min(Math.max(n,0),1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function signedAngleBetweenVectors(a, b, c) {
|
||||||
|
// Vector AB = (bx - ax, by - ay)
|
||||||
|
const ABx = b.x - a.x;
|
||||||
|
const ABy = b.y - a.y;
|
||||||
|
|
||||||
|
// Vector AC = (cx - ax, cy - ay)
|
||||||
|
const ACx = c.x - a.x;
|
||||||
|
const ACy = c.y - a.y;
|
||||||
|
|
||||||
|
// Dot product of AB and AC
|
||||||
|
const dotProduct = ABx * ACx + ABy * ACy;
|
||||||
|
|
||||||
|
// Magnitudes of AB and AC
|
||||||
|
const magnitudeAB = Math.sqrt(ABx * ABx + ABy * ABy);
|
||||||
|
const magnitudeAC = Math.sqrt(ACx * ACx + ACy * ACy);
|
||||||
|
|
||||||
|
// Cosine of the angle between AB and AC
|
||||||
|
const cosTheta = dotProduct / (magnitudeAB * magnitudeAC);
|
||||||
|
|
||||||
|
// Clamp the value to avoid floating point errors
|
||||||
|
const clampedCosTheta = Math.max(-1, Math.min(1, cosTheta));
|
||||||
|
|
||||||
|
// Angle in radians
|
||||||
|
const angleRadians = Math.acos(clampedCosTheta);
|
||||||
|
|
||||||
|
// Cross product to determine the sign of the angle
|
||||||
|
const crossProduct = ABx * ACy - ABy * ACx;
|
||||||
|
|
||||||
|
// If the cross product is positive, the angle is counterclockwise, otherwise it's clockwise
|
||||||
|
const signedAngle = crossProduct > 0 ? angleRadians : -angleRadians;
|
||||||
|
|
||||||
|
return signedAngle;
|
||||||
|
}
|
||||||
|
|
||||||
function drawBorderedRect(ctx, x, y, width, height, top, bottom, left, right) {
|
function drawBorderedRect(ctx, x, y, width, height, top, bottom, left, right) {
|
||||||
ctx.fillRect(x, y, width, height)
|
ctx.fillRect(x, y, width, height)
|
||||||
if (top) {
|
if (top) {
|
||||||
|
|
@ -797,6 +831,7 @@ export {
|
||||||
rgbToHex,
|
rgbToHex,
|
||||||
drawCheckerboardBackground,
|
drawCheckerboardBackground,
|
||||||
clamp,
|
clamp,
|
||||||
|
signedAngleBetweenVectors,
|
||||||
drawBorderedRect,
|
drawBorderedRect,
|
||||||
drawCenteredText,
|
drawCenteredText,
|
||||||
drawHorizontallyCenteredText,
|
drawHorizontallyCenteredText,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue