porous-cfd / app /static /js /visualization.js
Gallinator
visualization: add offset from click to control point when moving
059dc93 unverified
BSpline.prototype.getPlotlyCurve = function (nPoints) {
let x = [], y = []
let delta = 1 / nPoints
let domain = { l: this.knots[this.degree], h: this.knots[this.knots.length - 1 - this.degree] }
let pL = this.getValue(domain.l)
x.push(pL.x)
y.push(pL.y)
for (let i = 0; i <= nPoints; i++) {
let t = i * delta
if (t <= domain.l || t >= domain.h)
continue
let p = this.getValue(t)
x.push(p.x)
y.push(p.y)
}
x.push(pL.x)
y.push(pL.y)
return { "x": x, "y": y }
};
BSpline.prototype.getPlotlyControlPoints = function () {
let x = [], y = []
for (let i = 0; i < this.controlPoints.length - this.degree; i++) {
x.push(this.controlPoints[i].x)
y.push(this.controlPoints[i].y)
}
return { "x": x, "y": y }
}
function drawControlPoints(canvas) {
let ctx = canvas.getContext("2d")
for (let i = 1; i < this.controlPoints.length - 1; i++) {
ctx.beginPath()
let center = this.controlPoints[i]
ctx.arc(center.x, center.y, 10, 0, 2 * Math.PI)
ctx.strokeStyle = (i == 1 || i == this.controlPoints.length - 2) ? "blue" : "red";
ctx.lineWidth = 2
ctx.stroke()
}
};
function draw(canvas, nPoints) {
let ctx = canvas.getContext("2d")
ctx.lineWidth = 5
ctx.beginPath()
let delta = 1 / nPoints
let curP = this.getValue(0)
ctx.moveTo(curP.x, curP.y)
for (let i = 1; i <= nPoints; i++) {
curP = this.getValue(i * delta)
ctx.lineTo(curP.x, curP.y)
ctx.strokeStyle = "black"
ctx.lineCap = "round"
ctx.stroke()
}
if (this.closed) {
ctx.fillStyle = 'grey'
ctx.fill()
}
};
function denormalize(val, range) {
return val * (range[1] - range[0]) + range[0]
}
function clamp(v, l, h) {
return Math.min(Math.max(v, l), h)
}
function getPlotPixelSize(div) {
let clipRect = div._fullLayout._plots.xy.clipRect[0][0]
return { "x": clipRect.width.baseVal.value, "y": clipRect.height.baseVal.value }
}
function getLayerPos(div, eventData) {
let rect = div.getBoundingClientRect()
let margins = div._fullLayout.margin
return {
"x": eventData.clientX - rect.x - margins.l + window.scrollX,
"y": eventData.clientY - rect.y - margins.t + window.scrollY
}
}
function getPlotPos(div, eventData) {
let plotPos = getLayerPos(div, eventData)
let plotSize = getPlotPixelSize(div)
let x = plotPos.x / plotSize.x
x = clamp(x, 0, 1)
let y = 1 - plotPos.y / plotSize.y
y = clamp(y, 0, 1)
return {
"x": denormalize(x, div._fullLayout.xaxis.range),
"y": denormalize(y, div._fullLayout.yaxis.range)
}
}
function isInsidePlot(div, eventData) {
let docPos = {
"x": div.getBoundingClientRect().x + window.scrollX,
"y": div.getBoundingClientRect().y + window.scrollY
}
let margins = div._fullLayout.margin
let localX = eventData.clientX - docPos.x
let localY = eventData.clientY - docPos.y
let plotSize = getPlotPixelSize(div)
return localX > margins.l && localX < margins.l + plotSize.x && localY > margins.t && localY < margins.t + plotSize.y
}
class CurveEditor {
constructor(div, curve, selectThreshold) {
this.div = div
this.curve = curve
this.selectThreshold = selectThreshold
this.enabled = true
this.moving = false
this.movingId = false
this.isDeleteKeyDown = false
this.offset = { "x": 0, "y": 0 }
this.handler = { onupdate: () => { } }
window.focus()
this.div.ownerDocument.addEventListener("keydown", (event) => {
if (event.key == "Control")
this.isDeleteKeyDown = true
})
this.div.ownerDocument.addEventListener("keyup", (event) => {
if (event.key == "Control")
this.isDeleteKeyDown = false
})
this.div.ownerDocument.addEventListener("pointerdown", (evt) => {
if (!isInsidePlot(this.div, evt) || !this.enabled) return
let clickedPoint = getPlotPos(div, evt);
this.curve.controlPoints.forEach((p, i) => {
if (this.isWithinThreshold(clickedPoint, p) && this.isSelectable(i)) {
this.moving = true;
this.movingId = i;
this.offset.x = clickedPoint.x - p.x
this.offset.y = clickedPoint.y - p.y
}
})
});
this.div.ownerDocument.addEventListener("pointermove", (evt) => {
let found = false;
let mousePoint = getPlotPos(this.div, evt)
this.curve.controlPoints.forEach((p, i) => {
if (this.isWithinThreshold(mousePoint, p) && this.isSelectable(i))
found = found || true;
});
this.div.style.cursor = found ? "pointer" : "default";
if (!this.moving)
return
this.curve.moveControlPoint(this.movingId, mousePoint.x - this.offset.x, mousePoint.y - this.offset.y)
this.onupdate();
});
this.div.ownerDocument.addEventListener("pointerup", (evt) => {
if (!this.moving) return;
this.moving = false;
});
this.div.ownerDocument.addEventListener('contextmenu', (event) => {
if (!isInsidePlot(this.div, event) || !this.enabled) return
event.preventDefault();
let clickedPoint = getPlotPos(this.div, event)
if (this.isDeleteKeyDown) {
if (this.curve.controlPoints.length - this.curve.degree == 3)
return
this.deletePoint(clickedPoint)
}
else
this.insertPoint(clickedPoint)
this.onupdate();
});
}
deletePoint(clickedPoint) {
let minDist = Number.MAX_VALUE
let minId = 0
let nControls = this.curve.closed ? this.curve.controlPoints.length - this.curve.degree : this.curve.controlPoints.length
for (let i = 0; i < nControls; i++) {
let p = this.curve.controlPoints[i]
let dist = this.getDistance(clickedPoint, p)
if (dist < minDist) {
minDist = dist
minId = i
}
}
this.curve.deleteControlPoint(minId)
}
insertPoint(clickedPoint) {
let minDist = Number.MAX_VALUE
let minId = 0
let nControls = this.curve.closed ? this.curve.controlPoints.length - this.curve.degree : this.curve.controlPoints.length
for (let i = 0; i < nControls; i++) {
let p = this.curve.controlPoints[i]
let dist = this.getDistance(clickedPoint, p)
if (dist < minDist) {
minDist = dist
minId = i
}
}
let nextP = this.curve.controlPoints[(minId + 1) % nControls]
let prevP = this.curve.controlPoints[(minId - 1 + nControls) % nControls]
let minP = this.curve.controlPoints[minId]
let nextVec = { x: nextP.x - minP.x, y: nextP.y - minP.y }
let prevVec = { x: minP.x - prevP.x, y: minP.y - prevP.y }
let halfVec = { x: (nextVec.x + prevVec.x) / 2, y: (nextVec.y + prevVec.y) / 2 }
let clickedVec = { x: clickedPoint.x - minP.x, y: clickedPoint.y - minP.y }
let dot = halfVec.x * clickedVec.x + halfVec.y * clickedVec.y
if (dot > 0)
minId = (minId + 1) % nControls
this.curve.addControlPoint(new Point(clickedPoint.x, clickedPoint.y), minId);
}
getDistance(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2))
}
isWithinThreshold(pointer, tgt) {
return Math.abs(pointer.x - tgt.x) < this.selectThreshold && Math.abs(pointer.y - tgt.y) < this.selectThreshold
}
isSelectable(i) {
return i < this.curve.controlPoints.length - this.curve.degree
}
}