fsm-designer/js/simulate.js
2019-04-03 16:15:15 +02:00

223 lines
6.6 KiB
JavaScript

let simulationStates = [];
let simulationStepDuration = 500;
function simulate(word, singleCharMode = true) {
if(activeDocument === null) {
return false;
}
let steps = [];
if (singleCharMode) {
steps = word.split('');
} else {
steps = word.split(/ /g);
}
simulationStates.forEach(state => {
state.isActive = false;
});
simulationStates.splice();
const startConnections = documents[activeDocument].connections.filter(conn => conn instanceof StartConnection);
startConnections.forEach(connection => animations.push(new ConnectionAnimation(connection)));
simulationStates = startConnections.map(conn => conn.state);
// TODO: Check if there are no connections and throw error message to user
setTimeout(() => {
simulationStates.forEach(state => {
state.isActive = true;
state.activeTime = Date.now();
});
let stepIndex = 0;
const stepInterval = setInterval(() => {
if (stepIndex >= steps.length) {
clearInterval(stepInterval);
return;
}
// TODO: Check if there are any possibilities left or stop run
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) {
if(activeDocument === null)
return [];
return documents[activeDocument].connections.filter(connection => connection instanceof SelfConnection ? connection.state === state : connection.stateA === state);
}
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
}