2019-03-04 09:30:17 +00:00
|
|
|
let simulationStates = [];
|
|
|
|
let singleCharMode = true;
|
|
|
|
let simulationStepDuration = 500;
|
|
|
|
|
|
|
|
function simulate(word) {
|
2019-03-04 18:16:11 +00:00
|
|
|
if(activeDocument !== null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-04 09:30:17 +00:00
|
|
|
let steps = [];
|
|
|
|
|
|
|
|
if (singleCharMode) {
|
|
|
|
steps = word.split('');
|
|
|
|
} else {
|
|
|
|
steps = word.split(/ /g);
|
|
|
|
}
|
|
|
|
|
|
|
|
simulationStates.forEach(state => {
|
|
|
|
state.isActive = false;
|
|
|
|
});
|
|
|
|
simulationStates.splice();
|
|
|
|
|
2019-03-04 18:16:11 +00:00
|
|
|
const startConnections = documents[activeDocument].connections.filter(conn => conn instanceof StartConnection);
|
2019-03-04 09:30:17 +00:00
|
|
|
startConnections.forEach(connection => animations.push(new ConnectionAnimation(connection)));
|
|
|
|
simulationStates = startConnections.map(conn => conn.state);
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
simulationStates.forEach(state => {
|
|
|
|
state.isActive = true;
|
|
|
|
state.activeTime = Date.now();
|
|
|
|
});
|
|
|
|
|
|
|
|
let stepIndex = 0;
|
|
|
|
const stepInterval = setInterval(() => {
|
|
|
|
if (stepIndex >= steps.length) {
|
|
|
|
clearInterval(stepInterval);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
simulationStep(steps[stepIndex]);
|
|
|
|
stepIndex++;
|
|
|
|
}, simulationStepDuration * 4);
|
|
|
|
}, simulationStepDuration);
|
|
|
|
}
|
|
|
|
|
|
|
|
function simulationStep(input) {
|
|
|
|
const len = simulationStates.length;
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
const state = simulationStates[i];
|
|
|
|
const possibilities = getOutgoingConnections(state);
|
|
|
|
|
|
|
|
simulationStates.splice(0, 1);
|
|
|
|
state.isActive = false;
|
|
|
|
state.activeTime = Date.now();
|
|
|
|
|
|
|
|
possibilities.forEach(conn => {
|
|
|
|
const conditions = conn.text.split(/, ?/g);
|
|
|
|
|
|
|
|
if (input.match(new RegExp(conditions.join('|'), 'g'))) {
|
|
|
|
if (conn instanceof Connection) {
|
|
|
|
animations.push(new ConnectionAnimation(conn));
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
simulationStates.push(conn.stateB);
|
|
|
|
conn.stateB.isActive = true;
|
|
|
|
conn.stateB.activeTime = Date.now();
|
|
|
|
}, simulationStepDuration);
|
|
|
|
} else if (conn instanceof SelfConnection) {
|
|
|
|
animations.push(new SelfConnectionAnimation(conn));
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
simulationStates.push(conn.state);
|
|
|
|
conn.state.isActive = true;
|
|
|
|
conn.state.activeTime = Date.now();
|
|
|
|
}, simulationStepDuration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getOutgoingConnections(state) {
|
2019-03-04 18:16:11 +00:00
|
|
|
if(activeDocument === null)
|
|
|
|
return [];
|
|
|
|
return documents[activeDocument].connections.filter(connection => connection instanceof SelfConnection ? connection.state === state : connection.stateA === state);
|
2019-03-04 09:30:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ConnectionAnimation {
|
|
|
|
|
|
|
|
constructor(connection) {
|
|
|
|
this.connection = connection;
|
|
|
|
this.startTime = Date.now();
|
|
|
|
}
|
|
|
|
|
|
|
|
getSize(percent) {
|
|
|
|
if (percent < .33) {
|
|
|
|
return easeInOutCubic(percent * 3) * 10;
|
|
|
|
} else if (percent < .66) {
|
|
|
|
return 10;
|
|
|
|
} else {
|
|
|
|
return easeInOutCubic(1 - (percent - .8) * 3) * 10;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getPosLinear(percent, points) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
getPosCircular(percent, points) {
|
|
|
|
const distanceSquared = (points.end.x - points.start.x) ** 2 + (points.end.y - points.start.y) ** 2;
|
|
|
|
let phi = Math.acos(1 - (distanceSquared / (2 * points.circle.radius ** 2)));
|
|
|
|
|
|
|
|
if (distanceSquared > points.circle.radius ** 2) {
|
|
|
|
phi = 2 * Math.PI - phi;
|
|
|
|
}
|
|
|
|
|
|
|
|
let deltaPhi = phi * percent;
|
|
|
|
|
|
|
|
const startX = points.start.x - points.circle.x;
|
|
|
|
const startY = points.start.y - points.circle.y;
|
|
|
|
|
|
|
|
if (!points.isReversed) {
|
|
|
|
deltaPhi *= -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const x = startX * Math.cos(deltaPhi) + startY * Math.sin(deltaPhi) + points.circle.x;
|
|
|
|
const y = -startX * Math.sin(deltaPhi) + startY * Math.cos(deltaPhi) + points.circle.y;
|
|
|
|
|
|
|
|
return {x, y};
|
|
|
|
}
|
|
|
|
|
|
|
|
draw() {
|
|
|
|
const deltaTime = Date.now() - this.startTime;
|
|
|
|
const percent = Math.min(Math.max(deltaTime / simulationStepDuration, 0), 1);
|
|
|
|
|
|
|
|
let curX = 0;
|
|
|
|
let curY = 0;
|
|
|
|
|
|
|
|
const endPoints = this.connection instanceof StartConnection ? this.connection.getEndPoints() : this.connection.getEndPointsAndCircle();
|
|
|
|
|
|
|
|
if (endPoints.isCircle) {
|
|
|
|
const pos = this.getPosCircular(percent, endPoints);
|
|
|
|
|
|
|
|
curX = pos.x;
|
|
|
|
curY = pos.y;
|
|
|
|
} else {
|
|
|
|
const deltaX = endPoints.end.x - endPoints.start.x;
|
|
|
|
const deltaY = endPoints.end.y - endPoints.start.y;
|
|
|
|
|
|
|
|
curX = endPoints.start.x + deltaX * percent;
|
|
|
|
curY = endPoints.start.y + deltaY * percent;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.fillStyle = '#0f0';
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
|
|
ctx.arc(curX, curY, this.getSize(percent), 0, 2 * Math.PI);
|
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
|
|
|
if (percent >= 1) {
|
|
|
|
animations.splice(animations.findIndex(anim => anim === this), 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class SelfConnectionAnimation {
|
|
|
|
|
|
|
|
constructor(connection) {
|
|
|
|
this.connection = connection;
|
|
|
|
this.startTime = Date.now();
|
|
|
|
}
|
|
|
|
|
|
|
|
draw() {
|
|
|
|
const deltaTime = Date.now() - this.startTime;
|
|
|
|
const percent = Math.min(Math.max(deltaTime / simulationStepDuration, 0), 1);
|
|
|
|
|
|
|
|
const endPoints = this.connection.getEndPointsAndCircle();
|
|
|
|
|
|
|
|
const distanceSquared = (endPoints.end.x - endPoints.start.x) ** 2 + (endPoints.end.y - endPoints.start.y) ** 2;
|
|
|
|
let phi = Math.acos(1 - (distanceSquared / (2 * endPoints.circle.radius ** 2)));
|
|
|
|
|
|
|
|
if (distanceSquared > endPoints.circle.radius ** 2) {
|
|
|
|
phi = 2 * Math.PI - phi;
|
|
|
|
}
|
|
|
|
|
|
|
|
let deltaPhi = phi * percent;
|
|
|
|
|
|
|
|
const startX = endPoints.start.x - endPoints.circle.x;
|
|
|
|
const startY = endPoints.start.y - endPoints.circle.y;
|
|
|
|
|
|
|
|
if (!endPoints.isReversed) {
|
|
|
|
deltaPhi *= -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = startX * Math.cos(deltaPhi) + startY * Math.sin(deltaPhi) + endPoints.circle.x;
|
|
|
|
let y = -startX * Math.sin(deltaPhi) + startY * Math.cos(deltaPhi) + endPoints.circle.y;
|
|
|
|
|
|
|
|
ctx.fillStyle = '#0f0';
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
|
|
ctx.arc(x, y, 10, 0, 2 * Math.PI);
|
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
|
|
|
if (percent >= 1) {
|
|
|
|
animations.splice(animations.findIndex(anim => anim === this), 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function easeInOutCubic(t) {
|
|
|
|
return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
|
2019-03-05 18:56:42 +00:00
|
|
|
}
|