Spaces:
Running
on
Zero
Running
on
Zero
// Reference the shared typedefs file | |
/// <reference path="../types/typedefs.js" /> | |
import { app } from '../../scripts/app.js' | |
import { infoLogger } from './comfy_shared.js' | |
function B0(t) { | |
return (1 - t) ** 3 / 6 | |
} | |
function B1(t) { | |
return (3 * t ** 3 - 6 * t ** 2 + 4) / 6 | |
} | |
function B2(t) { | |
return (-3 * t ** 3 + 3 * t ** 2 + 3 * t + 1) / 6 | |
} | |
function B3(t) { | |
return t ** 3 / 6 | |
} | |
class CurveWidget { | |
constructor(...args) { | |
const [inputName, opts] = args | |
this.name = inputName || 'Curve' | |
this.type = 'FLOAT_CURVE' | |
this.selectedPointIndex = null | |
this.options = opts | |
this.value = this.value || { 0: { x: 0, y: 0 }, 1: { x: 1, y: 1 } } | |
} | |
drawBSpline(ctx, width, height, posY) { | |
const n = this.value.length - 1 | |
const numSegments = n - 2 | |
const numPoints = this.value.length | |
if (numPoints < 4) { | |
this.drawLinear(ctx, width, height, posY) | |
} else { | |
for (let j = 0; j <= numSegments; j++) { | |
for (let t = 0; t <= 1; t += 0.01) { | |
let pt = this.getBSplinePoint(j, t) | |
let x = pt.x * width | |
let y = posY + height - pt.y * height | |
if (t === 0) ctx.moveTo(x, y) | |
else ctx.lineTo(x, y) | |
} | |
} | |
ctx.stroke() | |
} | |
} | |
drawLinear(ctx, width, height, posY) { | |
for (let i = 0; i < Object.keys(this.value).length - 1; i++) { | |
let p1 = this.value[i] | |
let p2 = this.value[i + 1] | |
ctx.moveTo(p1.x * width, posY + height - p1.y * height) | |
ctx.lineTo(p2.x * width, posY + height - p2.y * height) | |
} | |
ctx.stroke() | |
} | |
getBSplinePoint(i, t) { | |
// Control points for this segment | |
const p0 = this.value[i] | |
const p1 = this.value[i + 1] | |
const p2 = this.value[i + 2] | |
const p3 = this.value[i + 3] | |
const x = B0(t) * p0.x + B1(t) * p1.x + B2(t) * p2.x + B3(t) * p3.x | |
const y = B0(t) * p0.y + B1(t) * p1.y + B2(t) * p2.y + B3(t) * p3.y | |
return { x, y } | |
} | |
/** | |
* @param {OnDrawWidgetParams} args | |
*/ | |
draw(...args) { | |
const hide = this.type !== 'FLOAT_CURVE' | |
if (hide) { | |
return | |
} | |
const [ctx, node, width, posY, height] = args | |
const [cw, ch] = this.computeSize(width) | |
ctx.beginPath() | |
ctx.fillStyle = '#000' | |
ctx.strokeStyle = '#fff' | |
ctx.lineWidth = 2 | |
// normalized coordinates -> canvas coordinates | |
for (let i = 0; i < Object.keys(this.value || {}).length - 1; i++) { | |
let p1 = this.value[i] | |
let p2 = this.value[i + 1] | |
ctx.moveTo(p1.x * cw, posY + ch - p1.y * ch) | |
ctx.lineTo(p2.x * cw, posY + ch - p2.y * ch) | |
} | |
ctx.stroke() | |
// points | |
Object.values(this.value || {}).forEach((point) => { | |
ctx.beginPath() | |
ctx.arc(point.x * cw, posY + ch - point.y * ch, 5, 0, 2 * Math.PI) | |
ctx.fill() | |
}) | |
} | |
mouse(event, pos, node) { | |
let x = pos[0] - node.pos[0] | |
let y = pos[1] - node.pos[1] | |
const width = node.size[0] | |
const height = 300 // TODO: compute | |
const posY = node.pos[1] | |
const localPos = { x: pos[0], y: pos[1] - LiteGraph.NODE_WIDGET_HEIGHT } | |
if (event.type === LiteGraph.pointerevents_method + 'down') { | |
console.debug('Checking if a point was clicked') | |
const clickedPointIndex = this.detectPoint(localPos, width, height) | |
if (clickedPointIndex !== null) { | |
this.selectedPointIndex = clickedPointIndex | |
} else { | |
this.addPoint(localPos, width, height) | |
} | |
return true | |
} else if ( | |
event.type === LiteGraph.pointerevents_method + 'move' && | |
this.selectedPointIndex !== null | |
) { | |
this.movePoint(this.selectedPointIndex, localPos, width, height) | |
return true | |
} else if ( | |
event.type === LiteGraph.pointerevents_method + 'up' && | |
this.selectedPointIndex !== null | |
) { | |
this.selectedPointIndex = null | |
return true | |
} | |
return false | |
} | |
callback(...args) { | |
//value, that, node, pos, event) { | |
} | |
detectPoint(localPos, width, height) { | |
const threshold = 20 // TODO: extract | |
const keys = Object.keys(this.value) | |
for (let i = 0; i < keys.length; i++) { | |
const key = keys[i] | |
const p = this.value[key] | |
const px = p.x * width | |
const py = height - p.y * height | |
if ( | |
Math.abs(localPos.x - px) < threshold && | |
Math.abs(localPos.y - py) < threshold | |
) { | |
return key | |
} | |
} | |
return null | |
} | |
addPoint(localPos, width, height) { | |
// add a new point based on click position | |
const normalizedPoint = { | |
x: localPos.x / width, | |
y: 1 - localPos.y / height, | |
} | |
const keys = Object.keys(this.value) | |
let insertIndex = keys.length | |
for (let i = 0; i < keys.length; i++) { | |
if (normalizedPoint.x < this.value[keys[i]].x) { | |
insertIndex = i | |
break | |
} | |
} | |
// shift | |
for (let i = keys.length; i > insertIndex; i--) { | |
this.value[i] = this.value[i - 1] | |
} | |
this.value[insertIndex] = normalizedPoint | |
} | |
movePoint(index, localPos, width, height) { | |
const point = this.value[index] | |
point.x = Math.max(0, Math.min(1, localPos.x / width)) | |
point.y = Math.max(0, Math.min(1, 1 - localPos.y / height)) | |
this.value[index] = point | |
} | |
computeSize(width) { | |
return [width, 300] | |
} | |
configure(data) { | |
} | |
} | |
app.registerExtension({ | |
name: 'mtb.curves', | |
getCustomWidgets: () => { | |
return { | |
/** | |
* @param {LGraphNode} node | |
* @param {str} inputName | |
* @param {[str,*]} inputData | |
* @param {*} app | |
* | |
*/ | |
FLOAT_CURVE: (node, inputName, inputData, app) => { | |
// const c = node.widgets.find((w) => w.type === "FLOAT_CURVE") | |
const wid = node.addCustomWidget(new CurveWidget(inputName, inputData)) | |
return { | |
widget: wid, | |
minWidth: 150, | |
minHeight: 30, | |
} | |
}, | |
} | |
}, | |
}) | |