|
const monitor = []; |
|
function eachMonitor(p, f) { |
|
monitor.forEach((m) => { |
|
if (m[0] === p[0] && m[1] === p[1]) { |
|
f(m); |
|
} |
|
}); |
|
} |
|
function isMonitored(p) { |
|
let result = false; |
|
eachMonitor(p, (m) => { |
|
result = true; |
|
}); |
|
return result; |
|
} |
|
|
|
|
|
|
|
function worldToLocal(pointA, pointB, pointC) { |
|
const vectorAB = [pointB[0] - pointA[0], pointB[1] - pointA[1]]; |
|
const vectorAC = [pointC[0] - pointA[0], pointC[1] - pointA[1]]; |
|
const dot1 = dot2D(vectorAB, vectorAC); |
|
const dot2 = dot2D(vectorAB, vectorAB); |
|
const localX = dot1 / dot2; |
|
const pointD = [pointA[0] + localX * vectorAB[0], pointA[1] + localX * vectorAB[1]]; |
|
|
|
|
|
const vectorCD = [pointD[0] - pointC[0], pointD[1] - pointC[1]]; |
|
let localY = Math.sqrt(dot2D(vectorCD, vectorCD)); |
|
|
|
|
|
const cross = cross2D(vectorAB, vectorAC); |
|
if (0 < cross) { |
|
localY = -localY; |
|
} |
|
|
|
return [localX, localY]; |
|
} |
|
|
|
function distPointFromSeg(pointA, pointB, pointC, lx, ly) { |
|
if (lx < 0) { |
|
const vectorAC = [pointC[0] - pointA[0], pointC[1] - pointA[1]]; |
|
return Math.sqrt(dot2D(vectorAC, vectorAC)); |
|
} |
|
if (1.0 < lx) { |
|
const vectorBC = [pointC[0] - pointB[0], pointC[1] - pointB[1]]; |
|
return Math.sqrt(dot2D(vectorBC, vectorBC)); |
|
} |
|
return Math.abs(ly); |
|
} |
|
|
|
function localToWorld([pointA, pointB], pointC) { |
|
const vectorAB = [pointB[0] - pointA[0], pointB[1] - pointA[1]]; |
|
|
|
const pointD = [pointA[0] + pointC[0] * vectorAB[0], pointA[1] + pointC[0] * vectorAB[1]]; |
|
const v = perpendicular2D(vectorAB); |
|
const vLength = Math.sqrt(dot2D(v, v)); |
|
if (vLength < 0.0001) { return pointD; } |
|
|
|
const newLength = pointC[1]; |
|
const factor = newLength / vLength; |
|
return [pointD[0] + factor * v[0], pointD[1] + factor * v[1]]; |
|
} |
|
|
|
function makeEdgeSegments(pose) { |
|
return limbSeq.map((segment) => { |
|
const p0 = pose[segment[0]]; |
|
const p1 = pose[segment[1]]; |
|
return [p0, p1]; |
|
}); |
|
} |
|
|
|
function makeNormalizeEdgeSegments(edges) { |
|
return edges.map((edge) => normalize2D([edge[1][0] - edge[0][0], edge[1][1] - edge[0][1]])); |
|
} |
|
|
|
const fieldDepth = 5; |
|
const FO0i = 0; |
|
const FO0t = 1; |
|
const FO0u = 2; |
|
const FO0d = 3; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calcJointRange( |
|
edgeSegments, normalizedEdgeSegments, |
|
edgeSegmentsForSideDecision, |
|
ep, ec, m) { |
|
|
|
const np = normalizedEdgeSegments[ep]; |
|
const nc = normalizedEdgeSegments[ec]; |
|
let pnp = perpendicular2D(np); |
|
let pnc = perpendicular2D(nc); |
|
let side = getSide(edgeSegmentsForSideDecision[ep][0], edgeSegmentsForSideDecision[ep][1], edgeSegmentsForSideDecision[ec][1], m); |
|
if (side < 0) { |
|
pnp = reverse2D(pnp); |
|
pnc = reverse2D(pnc); |
|
} |
|
return [pnp, pnc]; |
|
} |
|
|
|
function calcJointRangeWithOrder(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments, e0, m, order) { |
|
if (order < 0) { |
|
const e1 = findPrevEdgeIndex(e0); |
|
if (e1 < 0) { return null; } |
|
const [pn1, pn0] = calcJointRange(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments,e1, e0, m); |
|
const oldPivot = oldEdgeSegments[e0][0]; |
|
const newPivot = newEdgeSegments[e0][0]; |
|
return [pn0, pn1, oldPivot, newPivot, e1]; |
|
} else if (0 < order) { |
|
const e1 = findNextEdgeIndex(e0); |
|
if (e1 < 0) { return null; } |
|
const [pn0, pn1] = calcJointRange(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments, e0, e1, m); |
|
const oldPivot = oldEdgeSegments[e0][1]; |
|
const newPivot = newEdgeSegments[e0][1]; |
|
return [pn0, pn1, oldPivot, newPivot, e1]; |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
function buildWeightMap(size, pose) { |
|
const threshold = 512; |
|
|
|
const [w, h] = size; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const field = new Float32Array(w * h * fieldDepth) |
|
function fieldIndex(x, y) { |
|
return (y * w + x) * fieldDepth; |
|
} |
|
|
|
const edgeSegments = makeEdgeSegments(pose); |
|
const normalizedEdgeSegments = makeNormalizeEdgeSegments(edgeSegments); |
|
|
|
|
|
|
|
for (let y = 0 ; y < h ; y++) { |
|
for (let x = 0 ; x < w ; x++) { |
|
const m = [x, y]; |
|
let minDist = threshold; |
|
const fidx = fieldIndex(x, y); |
|
field[fidx+FO0i] = -1; |
|
for (let i = 0 ; i < limbSeq.length ; i++) { |
|
|
|
const s = edgeSegments[i]; |
|
const lq = worldToLocal(s[0], s[1], m); |
|
const dist = distPointFromSeg(s[0], s[1], m, lq[0], lq[1]); |
|
if (dist < minDist) { |
|
minDist = dist; |
|
field[fidx+FO0i] = i; |
|
field[fidx+FO0t] = lq[0]; |
|
field[fidx+FO0u] = lq[1]; |
|
field[fidx+FO0d] = dist; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return field; |
|
} |
|
|
|
function renderField(canvas2, size, field) { |
|
function blend(c0, w0, c1, w1) { |
|
const r = (c0[0] * w0 + c1[0] * w1); |
|
const g = (c0[1] * w0 + c1[1] * w1); |
|
const b = (c0[2] * w0 + c1[2] * w1); |
|
const a = (c0[3] * w0 + c1[3] * w1); |
|
return [r, g, b, a]; |
|
} |
|
|
|
const [w, h] = size; |
|
function fieldIndex(x, y) { |
|
return (y * w + x) * fieldDepth; |
|
} |
|
|
|
canvas2.width = w; |
|
canvas2.height = h; |
|
const ctx2 = canvas2.getContext('2d'); |
|
|
|
bindings = ctx2.createImageData(w, h); |
|
const data = bindings.data; |
|
for (let y = 0 ; y < h ; y++) { |
|
for (let x = 0 ; x < w ; x++) { |
|
const fidx = fieldIndex(x, y); |
|
const oi = (y * w + x) * 4; |
|
|
|
let c = [0, 0, 0, 64]; |
|
|
|
const e0 = field[fidx+FO0i]; |
|
if (0 <= e0) { |
|
c = colors[e0]; |
|
c[3] = 255; |
|
|
|
} |
|
data[oi + 0] = c[0]; |
|
data[oi + 1] = c[1]; |
|
data[oi + 2] = c[2]; |
|
data[oi + 3] = c[3]; |
|
} |
|
} |
|
ctx2.putImageData(bindings, 0, 0); |
|
|
|
return field; |
|
} |
|
|
|
function makeImageDataFromPicture(canvas3, size, picture) { |
|
const [w, h] = size; |
|
canvas3.width = w; |
|
canvas3.height = h; |
|
|
|
const ctx3 = canvas3.getContext("2d"); |
|
|
|
ctx3.drawImage(picture, 0, 0); |
|
|
|
return new ImageData(new Uint8ClampedArray(ctx3.getImageData(0, 0, w, h).data), w, h); |
|
} |
|
|
|
function animatePicture(canvas4, size, oldPose, newPose, srcImageData, initialPoseField) { |
|
const srcData = srcImageData.data; |
|
|
|
const [w, h] = size; |
|
|
|
const ctx4 = canvas4.getContext("2d"); |
|
canvas4.width = w; |
|
canvas4.height = h; |
|
const dstCtx = ctx4; |
|
const dstImageData = dstCtx.createImageData(w, h); |
|
const dstData = dstImageData.data; |
|
|
|
const field = buildWeightMap(size, newPose); |
|
const canvas2 = document.getElementById("canvas2"); |
|
|
|
drawBodyPoseTo(canvas2.getContext("2d"), [newPose]); |
|
|
|
function fieldIndex(x, y) { |
|
return (y * w + x) * fieldDepth; |
|
} |
|
|
|
const oldEdgeSegments = makeEdgeSegments(oldPose); |
|
const oldNormalizedEdgeSegments = makeNormalizeEdgeSegments(oldEdgeSegments); |
|
const newEdgeSegments = makeEdgeSegments(newPose); |
|
|
|
function oldEdgePoint(edgeIndex, p) { |
|
const v = oldEdgeSegments[edgeIndex]; |
|
return localToWorld(v, p); |
|
} |
|
|
|
function newEdgePoint(edgeIndex, p) { |
|
const v = newEdgeSegments[edgeIndex]; |
|
return localToWorld(v, p); |
|
} |
|
|
|
debugLines = []; |
|
|
|
for (let y = 0 ; y < h ; y++) { |
|
for (let x = 0 ; x < w ; x++) { |
|
|
|
const oi = (y * w + x) * 4; |
|
|
|
const fidx = fieldIndex(x, y); |
|
const e0 = field[fidx+FO0i]; |
|
if (e0 < 0) { |
|
dstData[oi + 0] = 0; |
|
dstData[oi + 1] = 0; |
|
dstData[oi + 2] = 0; |
|
dstData[oi + 3] = 255; |
|
continue; |
|
} |
|
|
|
const lq = [field[fidx+FO0t], field[fidx+FO0u]]; |
|
let e1 = -1; |
|
let order = 0; |
|
if (lq[0] < 0.5) { |
|
e1 = findPrevEdgeIndex(e0); |
|
} else if (0.5 <= lq[0]) { |
|
e1 = findNextEdgeIndex(e0); |
|
} |
|
if (0 <= e1) { |
|
if (lq[0] < 0) { |
|
order = -1; |
|
} else if (1 < lq[0]) { |
|
order = 1; |
|
} |
|
} |
|
|
|
let iq = oldEdgePoint(e0, lq); |
|
|
|
if (0 != order) { |
|
|
|
const mNew = [x,y]; |
|
const jointRange = calcJointRangeWithOrder( |
|
oldEdgeSegments, oldNormalizedEdgeSegments, |
|
newEdgeSegments, |
|
e0, mNew, order); |
|
const [pn0, pn1, oldPivot, newPivot, _] = jointRange; |
|
|
|
const pivotMNew = [x - newPivot[0], y - newPivot[1]]; |
|
const [w0, w1]= [angleBetween(pn0, pivotMNew), angleBetween(pivotMNew, pn1)]; |
|
|
|
let d0 = field[fidx+FO0d]; |
|
let mt = slerp2D(pn0, pn1, w0 / (w0 + w1)); |
|
iq = [oldPivot[0] + mt[0] * d0, oldPivot[1] + mt[1] * d0]; |
|
eachMonitor([x,y], () => { |
|
console.log('animate', x, y, ix, iy, e1, w0, w1, pn0, pn1); |
|
debugLines.push([oldPivot, pn0]); |
|
debugLines.push([oldPivot, pn1]); |
|
}); |
|
} |
|
|
|
iq = [Math.round(iq[0]), Math.round(iq[1])]; |
|
|
|
var initialOwner = initialPoseField[fieldIndex(iq[0], iq[1]) + FO0i]; |
|
if (0 <= initialOwner && initialOwner != e0 && initialOwner != e1) { |
|
|
|
dstData[oi + 0] = 0; |
|
dstData[oi + 1] = 0; |
|
dstData[oi + 2] = 0; |
|
dstData[oi + 3] = 0; |
|
} else { |
|
|
|
const si = (iq[1] * w + iq[0]) * 4; |
|
dstData[oi + 0] = srcData[si + 0]; |
|
dstData[oi + 1] = srcData[si + 1]; |
|
dstData[oi + 2] = srcData[si + 2]; |
|
dstData[oi + 3] = srcData[si + 3]; |
|
} |
|
} |
|
} |
|
|
|
dstCtx.putImageData(dstImageData, 0, 0); |
|
} |
|
|
|
function handleMicroscopeMouseMove(e) { |
|
const [x,y] = mouseCursor; |
|
|
|
const imageData = ctx.getImageData(x - 2, y - 2, 5, 5); |
|
const data = imageData.data; |
|
|
|
const microscope = document.getElementById('microscope'); |
|
const ctx2 = microscope.getContext('2d'); |
|
ctx2.clearRect(0, 0, microscope.width, microscope.height); |
|
for (let i = 0 ; i < 5 ; i++) { |
|
for (let j = 0 ; j < 5 ; j++) { |
|
const idx = (i * 5 + j) * 4; |
|
ctx2.fillStyle = `rgba(${data[idx]}, ${data[idx+1]}, ${data[idx+2]}, ${data[idx+3]})`; |
|
ctx2.fillRect(j * 10, i * 10, 10, 10); |
|
} |
|
} |
|
} |
|
|
|
function initMicroscope() { |
|
|
|
|
|
|
|
} |
|
|
|
|