motion tween

This commit is contained in:
Skyler Lehmkuhl 2024-12-02 20:06:50 -05:00
parent d162a9599b
commit 95834bb0e9
3 changed files with 267 additions and 83 deletions

View File

@ -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 } from './utils.js'; import { titleCase, getMousePositionFraction, getKeyframesSurrounding } from './utils.js';
const { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs; const { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs;
const { const {
open: openFileDialog, open: openFileDialog,
@ -240,43 +240,49 @@ let actions = {
} }
}, },
addImageObject: { addImageObject: {
create: (x, y, img, parent) => { create: (x, y, imgsrc, ix, parent) => {
redoStack.length = 0; // Clear redo stack redoStack.length = 0; // Clear redo stack
let action = { let action = {
shapeUuid: uuidv4(), shapeUuid: uuidv4(),
objectUuid: uuidv4(), objectUuid: uuidv4(),
x: x, x: x,
y: y, y: y,
width: img.width, src: imgsrc,
height: img.height, ix: ix,
ix: img.ix,
img: img.idx,
parent: parent.idx parent: parent.idx
} }
undoStack.push({name: "addImageObject", action: action}) undoStack.push({name: "addImageObject", action: action})
actions.addImageObject.execute(action) actions.addImageObject.execute(action)
}, },
execute: (action) => { execute: (action) => {
let imageObject = new GraphicsObject(action.objectUuid) let imageObject = new GraphicsObject(action.objectUuid)
let img = pointerList[action.img] // let img = pointerList[action.img]
let ct = { let img = new Image();
...context, img.onload = function() {
fillImage: img, let ct = {
strokeShape: false, ...context,
fillImage: img,
strokeShape: false,
}
let imageShape = new Shape(0, 0, ct, action.shapeUuid)
imageShape.addLine(img.width, 0)
imageShape.addLine(img.width, img.height)
imageShape.addLine(0, img.height)
imageShape.addLine(0, 0)
imageShape.update()
imageShape.regions[0].fillImage = img
imageShape.regions[0].filled = true
imageObject.addShape(imageShape)
let parent = pointerList[action.parent]
parent.addObject(
imageObject,
action.x-img.width/2 + (20*action.ix),
action.y-img.height/2 + (20*action.ix)
)
updateUI();
} }
let imageShape = new Shape(0, 0, ct, action.shapeUuid) img.src = action.src
imageShape.addLine(action.width, 0)
imageShape.addLine(action.width, action.height)
imageShape.addLine(0, action.height)
imageShape.addLine(0, 0)
imageShape.update()
imageObject.addShape(imageShape)
let parent = pointerList[action.parent]
parent.addObject(
imageObject,
action.x-action.width/2 + (20*action.ix),
action.y-action.height/2 + (20*action.ix)
)
}, },
rollback: (action) => { rollback: (action) => {
let shape = pointerList[action.shapeUuid] let shape = pointerList[action.shapeUuid]
@ -312,6 +318,111 @@ let actions = {
frame.keys = structuredClone(action.oldState) frame.keys = structuredClone(action.oldState)
} }
}, },
addFrame: {
create: () => {
redoStack.length = 0
let frames = []
for (let i=context.activeObject.activeLayer.frames.length; i<=context.activeObject.currentFrameNum; i++) {
frames.push(uuidv4())
}
let action = {
frames: frames,
layer: context.activeObject.activeLayer.idx
}
undoStack.push({name: 'addFrame', action: action})
actions.addFrame.execute(action)
},
execute: (action) => {
let layer = pointerList[action.layer]
for (let frame of action.frames) {
layer.frames.push(new Frame("normal", frame))
}
updateLayers()
},
rollback: (action) => {
let layer = pointerList[action.layer]
for (let _frame of action.frames) {
layer.frames.pop()
}
updateLayers()
}
},
addKeyframe: {
create: () => {
let frameNum = context.activeObject.currentFrameNum
let layer = context.activeObject.activeLayer
let formerType;
let addedFrames = 0;
if (frameNum >= layer.frames.length) {
formerType = "none"
addedFrames = frameNum - layer.frames.length
} else if (layer.frames[frameNum].frameType != "keyframe") {
formerType = layer.frames[frameNum].frameType
} else {
console.log("foolish")
return // Already a keyframe, nothing to do
}
redoStack.length = 0
let action = {
frameNum: frameNum,
object: context.activeObject.idx,
layer: layer.idx,
formerType: formerType,
addedFrames: addedFrames
}
undoStack.push({name: 'addKeyframe', action: action})
actions.addKeyframe.execute(action)
},
execute: (action) => {
// your code here
let object = pointerList[action.object]
let layer = pointerList[action.layer]
let latestFrame = object.getFrame(Math.max(action.frameNum-1, 0))
let newKeyframe = new Frame("keyframe")
for (let key in latestFrame.keys) {
newKeyframe.keys[key] = structuredClone(latestFrame.keys[key])
}
for (let shape of latestFrame.shapes) {
newKeyframe.shapes.push(shape.copy())
}
if (action.frameNum >= layer.frames.length) {
for (let i=layer.frames.length; i<action.frameNum; i++) {
layer.frames.push(new Frame())
}
layer.frames.push(newKeyframe)
} else if (layer.frames[action.frameNum].frameType != "keyframe") {
layer.frames[action.frameNum] = newKeyframe
}
updateLayers()
},
rollback: (action) => {
let layer = pointerList[action.layer]
if (action.formerType == "none") {
for (let i=0; i<action.addedFrames+1; i++) {
layer.frames.pop()
}
} else {
let layer = pointerList[action.layer]
layer.frames[action.frameNum].frameType = action.formerType
}
updateLayers()
}
},
addMotionTween: {
create: () => {
redoStack.length = 0
let action = {
}
undoStack.push({name: 'addMotionTween', action: action})
actions.addMotionTween.execute(action)
},
execute: (action) => {
// your code here
},
rollback: (action) => {
// your code here
}
},
} }
function uuidv4() { function uuidv4() {
@ -919,7 +1030,7 @@ class Shape {
ctx.lineCap = "round" ctx.lineCap = "round"
for (let region of this.regions) { for (let region of this.regions) {
// if (region.filled) continue; // if (region.filled) continue;
if (region.fillStyle && region.filled) { if ((region.fillStyle || region.fillImage) && region.filled) {
// ctx.fillStyle = region.fill // ctx.fillStyle = region.fill
if (region.fillImage) { if (region.fillImage) {
let pat = ctx.createPattern(region.fillImage, "no-repeat") let pat = ctx.createPattern(region.fillImage, "no-repeat")
@ -937,19 +1048,21 @@ class Shape {
ctx.fill() ctx.fill()
} }
} }
for (let curve of this.curves) { if (this.stroked) {
ctx.strokeStyle = curve.color for (let curve of this.curves) {
ctx.beginPath() ctx.strokeStyle = curve.color
ctx.moveTo(curve.points[0].x, curve.points[0].y) ctx.beginPath()
ctx.bezierCurveTo(curve.points[1].x, curve.points[1].y, ctx.moveTo(curve.points[0].x, curve.points[0].y)
curve.points[2].x, curve.points[2].y, ctx.bezierCurveTo(curve.points[1].x, curve.points[1].y,
curve.points[3].x, curve.points[3].y) curve.points[2].x, curve.points[2].y,
ctx.stroke() curve.points[3].x, curve.points[3].y)
ctx.stroke()
// Debug, show curve endpoints // Debug, show curve endpoints
// ctx.beginPath() // ctx.beginPath()
// ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI) // ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI)
// ctx.fill() // ctx.fill()
}
} }
// Debug, show quadtree // Debug, show quadtree
// this.quadtree.draw(ctx) // this.quadtree.draw(ctx)
@ -981,26 +1094,46 @@ class GraphicsObject {
return this.layers[this.currentLayer] return this.layers[this.currentLayer]
} }
get children() { get children() {
return this.layers[this.currentLayer].children return this.activeLayer.children
} }
get currentFrame() { get currentFrame() {
return this.getFrame(this.currentFrameNum) return this.getFrame(this.currentFrameNum)
} }
getFrame(num) { getFrame(num) {
if (this.layers[this.currentLayer].frames[num]) { if (this.activeLayer.frames[num]) {
if (this.layers[this.currentLayer].frames[num].frameType == "keyframe") { if (this.activeLayer.frames[num].frameType == "keyframe") {
return this.layers[this.currentLayer].frames[num] return this.activeLayer.frames[num]
} else if (this.layers[this.currentLayer].frames[num].frameType == "motion") { } else if (this.activeLayer.frames[num].frameType == "motion") {
let frameKeys = {}
const t = (num - this.activeLayer.frames[num].prevIndex) / (this.activeLayer.frames[num].nextIndex - this.activeLayer.frames[num].prevIndex);
console.log(this.activeLayer.frames[num].prev)
for (let key in this.activeLayer.frames[num].prev.keys) {
frameKeys[key] = {}
let prevKeyDict = this.activeLayer.frames[num].prev.keys[key]
let nextKeyDict = this.activeLayer.frames[num].next.keys[key]
for (let prop in prevKeyDict) {
frameKeys[key][prop] = (1 - t) * prevKeyDict[prop] + t * nextKeyDict[prop];
}
} else if (this.layers[this.currentLayer].frames[num].frameType == "shape") { }
let frame = new Frame("motion", "temp")
frame.keys = frameKeys
return frame
} else if (this.activeLayer.frames[num].frameType == "shape") {
} else { } else {
for (let i=num; i>=0; i--) { for (let i=Math.min(num, this.activeLayer.frames.length-1); i>=0; i--) {
if (this.layers[this.currentLayer].frames[i].frameType == "keyframe") { if (this.activeLayer.frames[i].frameType == "keyframe") {
return this.layers[this.currentLayer].frames[i] return this.activeLayer.frames[i]
} }
} }
} }
} else {
for (let i=Math.min(num, this.activeLayer.frames.length-1); i>=0; i--) {
if (this.activeLayer.frames[i].frameType == "keyframe") {
return this.activeLayer.frames[i]
}
}
} }
} }
get maxFrame() { get maxFrame() {
@ -1032,9 +1165,9 @@ class GraphicsObject {
let ctx = context.ctx; let ctx = context.ctx;
ctx.translate(this.x, this.y) ctx.translate(this.x, this.y)
ctx.rotate(this.rotation) ctx.rotate(this.rotation)
if (this.currentFrameNum>=this.maxFrame) { // if (this.currentFrameNum>=this.maxFrame) {
this.currentFrameNum = 0; // this.currentFrameNum = 0;
} // }
for (let shape of this.currentFrame.shapes) { for (let shape of this.currentFrame.shapes) {
shape.draw(context) shape.draw(context)
} }
@ -1361,31 +1494,30 @@ async function quit() {
function addFrame() { function addFrame() {
if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) { if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) {
for (let i=context.activeObject.activeLayer.frames.length; i<=context.activeObject.currentFrameNum; i++) { actions.addFrame.create()
context.activeObject.activeLayer.frames.push(new Frame())
}
updateLayers()
} }
} }
function addKeyframe() { function addKeyframe() {
let newKeyframe = new Frame("keyframe") console.log(context.activeObject.currentFrameNum)
let latestFrame = context.activeObject.getFrame(Math.max(context.activeObject.currentFrameNum-1, 0)) actions.addKeyframe.create()
for (let key in latestFrame.keys) { }
newKeyframe.keys[key] = latestFrame.keys[key]
} function addMotionTween() {
for (let shape of latestFrame.shapes) { let frames = context.activeObject.activeLayer.frames
newKeyframe.shapes.push(shape.copy()) let currentFrame = context.activeObject.currentFrameNum
} let {lastKeyframeBefore, firstKeyframeAfter} = getKeyframesSurrounding(frames, currentFrame)
if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) { if ((lastKeyframeBefore != undefined) && (firstKeyframeAfter != undefined)) {
for (let i=context.activeObject.activeLayer.frames.length; i<context.activeObject.currentFrameNum; i++) { for (let i=lastKeyframeBefore + 1; i<firstKeyframeAfter; i++) {
context.activeObject.activeLayer.frames.push(new Frame()) frames[i].frameType = "motion"
frames[i].prev = frames[lastKeyframeBefore]
frames[i].next = frames[firstKeyframeAfter]
frames[i].prevIndex = lastKeyframeBefore
frames[i].nextIndex = firstKeyframeAfter
} }
context.activeObject.activeLayer.frames.push(newKeyframe)
} else if (context.activeObject.activeLayer.frames[context.activeObject.currentFrameNum].frameType != "keyframe") {
context.activeObject.activeLayer.frames[context.activeObject.currentFrameNum] = newKeyframe
} }
updateLayers() updateLayers()
console.log(frames)
} }
function stage() { function stage() {
@ -1407,16 +1539,26 @@ function stage() {
if (item.kind == "file") { if (item.kind == "file") {
let file = item.getAsFile() let file = item.getAsFile()
if (imageTypes.includes(file.type)) { if (imageTypes.includes(file.type)) {
let img = new Image() let img = new Image();
img.src = window.URL.createObjectURL(file) let reader = new FileReader();
img.ix = i
img.idx = uuidv4() // Read the file as a data URL
pointerList[img.idx] = img reader.readAsDataURL(file);
img.onload = function() { reader.ix = i
actions.addImageObject.create(
mouse.x, mouse.y, img, context.activeObject) reader.onload = function(event) {
updateUI() let imgsrc = event.target.result; // This is the data URL
} // console.log(imgsrc)
// img.onload = function() {
actions.addImageObject.create(
mouse.x, mouse.y, imgsrc, reader.ix, context.activeObject);
// };
};
reader.onerror = function(error) {
console.error("Error reading file as data URL", error);
};
} }
i++; i++;
} }
@ -2043,8 +2185,10 @@ function updateLayers() {
let mouse = getMousePos(layerTrack, e) let mouse = getMousePos(layerTrack, e)
let frameNum = parseInt(mouse.x/25) let frameNum = parseInt(mouse.x/25)
context.activeObject.currentFrameNum = frameNum context.activeObject.currentFrameNum = frameNum
console.log(context.activeObject )
updateLayers() updateLayers()
updateMenu() updateMenu()
updateUI()
}) })
let highlightedFrame = false let highlightedFrame = false
layer.frames.forEach((frame, i) => { layer.frames.forEach((frame, i) => {
@ -2055,10 +2199,8 @@ function updateLayers() {
frameEl.classList.add("active") frameEl.classList.add("active")
highlightedFrame = true highlightedFrame = true
} }
console.log(frame.frameType)
if (frame.frameType == "keyframe") { frameEl.classList.add(frame.frameType)
frameEl.classList.add("keyframe")
}
layerTrack.appendChild(frameEl) layerTrack.appendChild(frameEl)
}) })
if (!highlightedFrame) { if (!highlightedFrame) {
@ -2172,6 +2314,11 @@ async function updateMenu() {
newFrameMenuItem, newFrameMenuItem,
newKeyframeMenuItem, newKeyframeMenuItem,
deleteFrameMenuItem, deleteFrameMenuItem,
{
text: "Add Motion Tween",
enabled: activeFrame && (!activeKeyframe),
action: addMotionTween
},
{ {
text: "Return to start", text: "Return to start",
enabled: false, enabled: false,

View File

@ -305,6 +305,21 @@ button {
background-color: #222; /* Set the color of the circle (black in this case) */ background-color: #222; /* Set the color of the circle (black in this case) */
margin-bottom: 5px; margin-bottom: 5px;
} }
.frame.motion {
background-color: #7a00b3;
border: none;
}
.frame.motion:hover, .frame.motion.active {
background-color: #530379;
border: none;
}
/* :nth-child(1 of .frame.motion) {
background-color: blue;
}
:nth-last-child(1 of .frame.motion) {
background-color: red;
} */
.frame-highlight { .frame-highlight {
background-color: red; background-color: red;
width: 25px; width: 25px;

View File

@ -19,4 +19,26 @@ function getMousePositionFraction(event, element) {
return 0; // If neither class is present, return 0 (or handle as needed) return 0; // If neither class is present, return 0 (or handle as needed)
} }
export { titleCase, getMousePositionFraction }; function getKeyframesSurrounding(frames, index) {
let lastKeyframeBefore = undefined;
let firstKeyframeAfter = undefined;
// Find the last keyframe before the given index
for (let i = index - 1; i >= 0; i--) {
if (frames[i].frameType === "keyframe") {
lastKeyframeBefore = i;
break;
}
}
// Find the first keyframe after the given index
for (let i = index + 1; i < frames.length; i++) {
if (frames[i].frameType === "keyframe") {
firstKeyframeAfter = i;
break;
}
}
return { lastKeyframeBefore, firstKeyframeAfter };
}
export { titleCase, getMousePositionFraction, getKeyframesSurrounding };