├── LICENSE
├── README.md
├── animation
├── 1example_animation.mov
├── 1example_fullAnimation.mov
├── 1example_keynote.key
├── 2example_fullAnimation.mov
├── 2example_keynote.key
├── 2example_partial_animation.mov
└── gifs
│ ├── 1.inet-full-simulation.gif
│ └── 2.inet-full-simulation.gif
├── index.html
├── index.js
└── nasic-render.gif
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016-2019 Ethereum Foundation
4 | Copyright (c) 2019 Sunshine Cybernetics
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nasic Render
2 |
3 | This tool is used to create an animation of the reduction process of interaction nets for [Nasic](https://github.com/MaiaVictor/Nasic) (N-Ary Symmetric Interaction Combinators).
4 |
5 | ## Usage
6 | > It's recommended have a sketch before defining the type and position of the nodes.
7 |
8 | 1. Go to the root folder and run a local server.
9 | 2. Open `http://localhost:8080` to visualize the canvas.
10 |
11 | Edit function `makeNodes()` to setup nodes properties.
12 | We start creating an empty node's array, an initial node and the other nodes.
13 |
14 | ``` javascript
15 | function makeNodes() {
16 | var nodes = [];
17 | var initialNode = new Node(0, {x: width * 0.47 - 5, y: height * 0.05}, getRadianFromAngle());
18 | var node0 = new Node(1, {x: width * 0.5, y: height * 0.2}, getRadianFromAngle(90));
19 | var node1 = new Node(1, {x: width * 0.3, y: height * 0.40}, getRadianFromAngle());
20 | ...
21 | }
22 | ```
23 |
24 | After pushing the nodes into the array and identifing them, we have to connect their ports. Again, it's highly recommended have a sketch before doing this.
25 | ``` javascript
26 | // Connections between ports
27 | connectPorts([node0, 0], [node1, 0]);
28 | connectPorts([node0, 1], [node4, 0]);
29 | connectPorts([node0, 2],[initialNode, 0]);
30 | ...
31 | ```
32 |
33 | ## Making an animation
34 | - **Single click**: select an element
35 | - **Click and move**: updates an element position. Can be done in nodes and pivots.
36 | - **Click and use arrow keys**: updates an element angle, makes a rotation
37 | - **Space bar**: saves an state of nodes' info as position, rotating, ports, etc.
38 | - **Press crtl/cmd and click** a node to check if can do a transformation (reduction or duplication). If it can transform, the transformation will occur, otherwise nothing happens.
39 | - **Press 'x'** to remove the last state of nodes saved
40 | - **Press 'h'** to hide pivots position
41 | - **Press 'p'** to play an animation
42 |
43 | > Tip: to make a smooth animation, do small changes on the position and rotation of the elements and save them. More states saved, more smooth is the animation.
44 |
45 |
46 |
--------------------------------------------------------------------------------
/animation/1example_animation.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/1example_animation.mov
--------------------------------------------------------------------------------
/animation/1example_fullAnimation.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/1example_fullAnimation.mov
--------------------------------------------------------------------------------
/animation/1example_keynote.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/1example_keynote.key
--------------------------------------------------------------------------------
/animation/2example_fullAnimation.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/2example_fullAnimation.mov
--------------------------------------------------------------------------------
/animation/2example_keynote.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/2example_keynote.key
--------------------------------------------------------------------------------
/animation/2example_partial_animation.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/2example_partial_animation.mov
--------------------------------------------------------------------------------
/animation/gifs/1.inet-full-simulation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/gifs/1.inet-full-simulation.gif
--------------------------------------------------------------------------------
/animation/gifs/2.inet-full-simulation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/animation/gifs/2.inet-full-simulation.gif
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | class Node {
2 | constructor(label, position, angle) {
3 | this.id = null;
4 | this.label = label; // 0: initial, 1: white node, 2: black node
5 | this.position = position;
6 | this.angle = angle; // angle for port 0
7 | this.ports = [null, null, null]; // [[node0, 0], [node1, 1], [node2, 2]]
8 | // Pivots starts in the same positon as the ports. Each index of the array represents a port.
9 | this.pivots = [{x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0}];
10 | this.radius = 20;
11 | }
12 |
13 | getPortPosition(slot) {
14 | var add_angle = [0, getRadianFromAngle(240), getRadianFromAngle(120)][slot];
15 | var position = add([this.position.x, this.position.y], rotate([this.radius, 0], this.angle + add_angle));
16 | return {x: position[0], y: position[1]};
17 | }
18 |
19 | }
20 |
21 | // Size for canvas element
22 | const height = 500;
23 | const width = 500;
24 | /**
25 | * Represent a thing clicked. Can be of type Node or Pivot.
26 | * Type node: ["node", node]
27 | * Type pivot: ["pivot", node, port]
28 | */
29 | var elementSelected = null;
30 | // A node that will change it's angle. Type node: ["node", node]
31 | var elementClicked = null;
32 |
33 | // -- Keys auxiliars --
34 | var animate = null;
35 | var ctrlPressed = false;
36 | var hidePivots = false;
37 |
38 | var prevPositionMovement = []; // [{x: 0, y: 0}] Adds all previous positions for elements moving. Does not identifies which objects move, only the position
39 |
40 | var selectionColor = 'green';
41 | var nodeIdCounter = 0;
42 |
43 | // Nodes
44 | var nodes = makeNodes();
45 |
46 | // An Node array recording the changes of the positions
47 | var keyframes = []; // [[node0, node1, node2..], [node0, node1, node2...] ...]
48 |
49 | // -------- Edit this code to create your net ---------
50 | // Defines the properties of each node
51 | function makeNodes() {
52 | var nodes = [];
53 | var initialNode = new Node(0, {x: width * 0.47 - 5, y: height * 0.05}, getRadianFromAngle());
54 | var node0 = new Node(1, {x: width * 0.5, y: height * 0.2}, getRadianFromAngle(90));
55 | var node1 = new Node(1, {x: width * 0.3, y: height * 0.40}, getRadianFromAngle());
56 | var node2 = new Node(2, {x: width * 0.20, y: height * 0.60}, getRadianFromAngle());
57 | // -10 is to align an upside down node with the others
58 | var node3 = new Node(1, {x: width * 0.40, y: height * 0.60 - 10}, getRadianFromAngle(90));
59 | var node4 = new Node(1, {x: width - (width * 0.3), y: height * 0.40}, getRadianFromAngle());
60 |
61 | nodes.push(initialNode);
62 | nodes.push(node0);
63 | nodes.push(node1);
64 | nodes.push(node2);
65 | nodes.push(node3);
66 | nodes.push(node4);
67 |
68 | for (var i = 0; i < nodes.length; i++) {
69 | nodes[i].id = nodeIdCounter++;
70 | }
71 |
72 | // Connections between ports
73 | connectPorts([node0, 0], [node1, 0]);
74 | connectPorts([node0, 1], [node4, 0]);
75 | connectPorts([node0, 2],[initialNode, 0]);
76 | connectPorts([initialNode, 1], [initialNode, 2]);
77 |
78 | connectPorts([node1, 1], [node2, 0]);
79 | connectPorts([node1, 2], [node3, 2]);
80 |
81 | connectPorts([node2, 1], [node3, 0]);
82 | connectPorts([node2, 2], [node3, 1]);
83 |
84 | connectPorts([node4, 1], [node4, 2]);
85 |
86 | return nodes;
87 | }
88 |
89 | function setupCanvas(canvas) {
90 | var context = canvas.getContext("2d");
91 | if (window.devicePixelRatio > 1) {
92 | var canvasWidth = canvas.width;
93 | var canvasHeight = canvas.height;
94 |
95 | canvas.width = canvasWidth * window.devicePixelRatio;
96 | canvas.height = canvasHeight * window.devicePixelRatio;
97 | canvas.style.width = canvasWidth;
98 | canvas.style.height = canvasHeight;
99 |
100 | context.scale(window.devicePixelRatio, window.devicePixelRatio);
101 | }
102 | return context;
103 | }
104 |
105 | window.onload = function() {
106 | var canvas = document.getElementById("canvas");
107 | var context = setupCanvas(canvas);
108 |
109 | setInitialPositionForPivots(nodes);
110 |
111 | // Calls a function or evaluates an expression at specified intervals
112 | setInterval(() => {
113 | context.clearRect(0, 0, canvas.width, canvas.height);
114 |
115 | if (animate) {
116 | var animNodes = setupKeyframeForAnimation();
117 | for (var i = 0; i < animNodes.length; i++) {
118 | drawElements(context, animNodes[i]);
119 | };
120 | } else {
121 | for (var i = 0; i < nodes.length; i++) {
122 | drawElements(context, nodes[i]);
123 | };
124 | }
125 |
126 | }, 1000/30);
127 |
128 | // -- Rotation --
129 | canvas.onclick = function(e) {
130 | var positionClicked = [e.offsetX, e.offsetY];
131 | var maxRadiusDistance = 10;
132 | elementClicked = null;
133 |
134 | for (var i = 0; i < nodes.length; i++) {
135 | // Checks if any node was clicked
136 | var distanceFromNode = getDistanceBetween([nodes[i].position.x, nodes[i].position.y], positionClicked);
137 | if (distanceFromNode <= maxRadiusDistance) {
138 | elementClicked = nodes[i];
139 | prevPositionMovement.push(elementClicked.position);
140 | // If clicking holding command
141 | if (e.metaKey) {
142 | checkTransformation(elementClicked);
143 | }
144 | }
145 | }
146 |
147 | }
148 |
149 | // -- Drag and drop actions --
150 | canvas.onmousedown = function(e) {
151 | var positionClicked = [e.offsetX, e.offsetY];
152 | var maxRadiusDistance = 10;
153 |
154 | elementSelected = null;
155 | elementClicked = null;
156 |
157 | // Check if the initial node was clicked
158 | var distanceFromInitialNode = getDistanceBetween([nodes[0].position.x, nodes[0].position.y], positionClicked);
159 |
160 | for (var i = 0; i < nodes.length; i++) {
161 | // Checks if any node was clicked
162 | var distanceFromNode = getDistanceBetween([nodes[i].position.x, nodes[i].position.y], positionClicked);
163 | if (distanceFromNode <= maxRadiusDistance) {
164 | elementSelected = ["node", nodes[i]];
165 | prevPositionMovement.push(nodes[i].position);
166 | updatePivotsPosition(nodes[i]);
167 | }
168 | // Check if any pivot was clicked
169 | for (var j = 0; j < 3; j++) {
170 | var distanceFromPivot = getDistanceBetween([nodes[i].pivots[j].x, nodes[i].pivots[j].y], positionClicked);
171 | if (distanceFromPivot <= maxRadiusDistance) {
172 | elementSelected = ["pivot", nodes[i], j];
173 | }
174 | }
175 | }
176 |
177 | };
178 |
179 | canvas.onmousemove = function(e) {
180 | if (elementSelected) {
181 | var positionClicked = {x: e.offsetX, y: e.offsetY};
182 | var node = elementSelected[1];
183 | // Check the type of the element selected
184 | if (elementSelected[0] === "pivot") {
185 | var pivotPort = elementSelected[2];
186 | node.pivots[pivotPort] = positionClicked;
187 | } else {
188 | node.position = positionClicked
189 | updatePivotsPosition(node);
190 | }
191 | }
192 | }
193 |
194 | canvas.onmouseup = function(e) {
195 | elementSelected = null;
196 | }
197 | }
198 |
199 | function setupKeyframeForAnimation() {
200 | var animKeyframe = (currentKeyframe + ((Date.now() - animate) / 1000)) % keyframes.length;
201 | var nodesA = keyframes[Math.floor(animKeyframe)];
202 | var nodesB = keyframes[Math.floor(animKeyframe + 1) % keyframes.length];
203 | var animNodes = copyNodes(nodesA);
204 |
205 | // There was no reduction or duplication
206 | if (nodesA.length === nodesB.length) {
207 | // updates position, angle and pivots of a node
208 | for (var i = 0; i < animNodes.length; ++i) {
209 | var t = animKeyframe % 1;
210 | var {x: ax, y: ay} = nodesA[i].position;
211 | var {x: bx, y: by} = nodesB[i].position;
212 | var aa = nodesA[i].angle;
213 | var ba = nodesB[i].angle;
214 | animNodes[i].position = {x: ax+(bx-ax)*t, y: ay+(by-ay)*t};
215 | animNodes[i].angle = aa + (ba - aa) * t;
216 |
217 | for (var j = 0; j < 3; j++) {
218 | var {x: pax, y: pay} = nodesA[i].pivots[j];
219 | var {x: pbx, y: pby} = nodesB[i].pivots[j];
220 | animNodes[i].pivots[j] = {x: pax+(pbx-pax)*t, y: pay+(pby-pay)*t};
221 | }
222 | }
223 | }
224 | return animNodes;
225 | }
226 |
227 |
228 | // --- Keyboard actions ---
229 | window.addEventListener("keydown", keysPressed, false);
230 |
231 | function keysPressed(e) {
232 | var key = e.keyCode;
233 |
234 | if (elementClicked) {
235 | switch (key) {
236 | case (37): // left
237 | elementClicked.angle = elementClicked.angle - getRadianFromAngle(5);
238 | break;
239 | case (39): // right
240 | elementClicked.angle = elementClicked.angle + getRadianFromAngle(5);
241 | break;
242 | case (90): // ctr+z or cmd+z
243 | prevPositionMovement.pop(); // removes the actual position
244 | if (prevPositionMovement.length > 0) {
245 | elementClicked.position = elementoMoving.pop();
246 | }
247 | break;
248 | }
249 | }
250 |
251 | switch (key) {
252 | case (91): // ctrl or command
253 | case (93):
254 | ctrlPressed = true;
255 | break;
256 | case (32): // space bar
257 | keyframes.push(copyNodes(nodes));
258 | break;
259 | case (80): // letter p
260 | // plays an animation to change keyframes
261 | animateKeyframe();
262 | break;
263 | case (68): // letter d
264 | // load the next keyframe
265 | increaseKeyframe();
266 | break;
267 | case (72): // letter h
268 | // hide and show the pivots
269 | hidePivots = !hidePivots;
270 | break;
271 | case (88): // letter x
272 | if (keyframes.length > 1) {
273 | keyframes.pop();
274 | nodes = keyframes[keyframes.length - 1];
275 | }
276 | break;
277 | }
278 | }
279 |
280 | function keysReleased(e) {
281 | ctrlPressed = false
282 | }
283 | // Makes a copy of nodes values
284 | function copyNodes(nodes) {
285 | var copy = [];
286 | // Create a copy of nodes
287 | for (var i = 0; i < nodes.length; i++) {
288 | var node = new Node(nodes[i].label, nodes[i].position, nodes[i].angle);
289 | node.id = nodes[i].id;
290 | node.pivots[0] = {x: nodes[i].pivots[0].x, y: nodes[i].pivots[0].y};
291 | node.pivots[1] = {x: nodes[i].pivots[1].x, y: nodes[i].pivots[1].y};
292 | node.pivots[2] = {x: nodes[i].pivots[2].x, y: nodes[i].pivots[2].y};
293 | copy.push(node);
294 | }
295 | // Setup the new ports references
296 | for (var i = 0; i < copy.length; i++) { // in each node of nodes
297 | for (var j = 0; j < copy.length; j++) { // search in the array of copies
298 | // for the associated node for each port
299 | // Set infos for port 0
300 | if (nodes[i].ports[0][0].id === copy[j].id) {
301 | var nodeForPort0 = copy[j];
302 | var connectingOnPort = nodes[i].ports[0][1];
303 | copy[i].ports[0] = [nodeForPort0, connectingOnPort];
304 | }
305 | // Set infos for port 1
306 | if (nodes[i].ports[1][0].id === copy[j].id) {
307 | var nodeForPort1 = copy[j];
308 | var connectingOnPort = nodes[i].ports[1][1];
309 | copy[i].ports[1] = [nodeForPort1, connectingOnPort];
310 | }
311 | // Set infos for port 2
312 | if (nodes[i].ports[2][0].id === copy[j].id) {
313 | var nodeForPort2 = copy[j];
314 | var connectingOnPort = nodes[i].ports[2][1];
315 | copy[i].ports[2] = [nodeForPort2, connectingOnPort];
316 | }
317 | }
318 | }
319 | return copy;
320 | }
321 |
322 | var currentKeyframe = 0;
323 | // -- Animation --
324 | // Does an automatic animation from one keyframe to another
325 | function animateKeyframe() {
326 | if (animate) {
327 | animate = null;
328 | } else {
329 | animate = Date.now();
330 | }
331 | }
332 |
333 | function increaseKeyframe() {
334 | currentKeyframe = (currentKeyframe + 1) % keyframes.length;
335 | nodes = keyframes[currentKeyframe];
336 | }
337 |
338 |
339 | // -- Transformation --
340 | /*
341 | Evaluate if the node clicked can do a transformation. There are 2 types:
342 | 1- Nodes with the same label: reduction
343 | 2- Nodes with different labels: duplication
344 | */
345 | function checkTransformation(node) {
346 | var pairToTransform = node.ports[0][0]; // get the node that the current node is connecting on port 0
347 |
348 | if (pairToTransform.ports[0][0] === node && // check if the other node on port 0 is equal to the current node
349 | (node !== nodes[0] && pairToTransform !== nodes[0])) { // initial node don't reduce
350 | if (node.label === pairToTransform.label) { // reduction
351 | reduceNodes(node, pairToTransform);
352 | } else { // duplication
353 | duplicateNodes(node, pairToTransform);
354 | }
355 | // remove the current nodes from the array of nodes
356 | nodes = nodes.filter(aux_node => (aux_node !== node && aux_node !== pairToTransform));
357 | // setInitialPositionForPivots(nodes);
358 | }
359 | }
360 |
361 | // -- Reduction --
362 | // Occurs between nodes with the same label. Rewrite the ports for both Nodes taking place the ports of the other one.
363 | function reduceNodes(nodeA, nodeB) {
364 | // ports have the type: [node, portNumber]
365 | for (var i = 0; i < 3; i++) {
366 | // get the node associated with Port 1
367 | var nodeA_port_dest = nodeA.ports[i][0];
368 | var nodeB_port_dest = nodeB.ports[i][0];
369 | // new port that Port1 has to connect
370 | var a_destPort = nodeA.ports[i][1];
371 | var b_destPort = nodeB.ports[i][1];
372 | connectPorts([nodeA_port_dest, a_destPort], [nodeB_port_dest, b_destPort]);
373 | }
374 | }
375 |
376 | // // -- Duplication --
377 | // // Occurs between nodes with different labels. The nodes pass through each-other, duplicating themselves
378 | function duplicateNodes(nodeA, nodeB) {
379 | var xPositionLeft = nodeA.position.x - (nodeA.radius * 1.2);
380 | var xPositionRight = nodeA.position.x + (nodeA.radius * 1.2);
381 | var yPositionUp = nodeA.position.y;
382 | var yPositionDown = nodeA.position.y + (nodeA.radius * 2.5);
383 |
384 | var nodeA_leftUp = new Node(nodeA.label, {x: xPositionLeft, y: yPositionUp}, getRadianFromAngle());
385 | var nodeA_rightUp = new Node(nodeA.label, {x: xPositionRight, y: yPositionUp}, getRadianFromAngle());
386 | var nodeB_leftDown = new Node(nodeB.label, {x: xPositionLeft, y: yPositionDown}, getRadianFromAngle(90));
387 | var nodeB_rightDown = new Node(nodeB.label, {x: xPositionRight, y: yPositionDown}, getRadianFromAngle(90));
388 |
389 | nodeA_leftUp.id = nodeIdCounter++;
390 | nodeA_rightUp.id = nodeIdCounter++;
391 | nodeB_leftDown.id = nodeIdCounter++;
392 | nodeB_rightDown.id = nodeIdCounter++;
393 |
394 | nodes.push(nodeA_leftUp);
395 | nodes.push(nodeA_rightUp);
396 | nodes.push(nodeB_leftDown);
397 | nodes.push(nodeB_rightDown);
398 |
399 | connectPorts([nodeA_leftUp, 0], [nodeB.ports[1][0], nodeB.ports[1][1]]);
400 | connectPorts([nodeA_rightUp, 0], [nodeB.ports[2][0], nodeB.ports[2][1]]);
401 |
402 | connectPorts([nodeA_leftUp, 1], [nodeB_leftDown, 2]);
403 | connectPorts([nodeA_leftUp, 2], [nodeB_rightDown, 2]);
404 |
405 | connectPorts([nodeA_rightUp, 1], [nodeB_leftDown, 1]);
406 | connectPorts([nodeA_rightUp, 2], [nodeB_rightDown, 1]);
407 |
408 | connectPorts([nodeB_leftDown, 0],[nodeA.ports[2][0], nodeA.ports[2][1]]);
409 | connectPorts([nodeB_rightDown, 0],[nodeA.ports[1][0], nodeA.ports[1][1]]);
410 | setInitialPositionForPivots(nodes);
411 | }
412 |
413 |
414 | // ----- Auxiliar functions -----
415 | // Gets the distance between 2 points
416 | function getDistanceBetween([ax, ay], [bx, by]) {
417 | return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2));
418 | }
419 |
420 | // Add two vectors returning a new one
421 | function add([ax, ay], [bx, by]) {
422 | return [ax + bx, ay + by];
423 | }
424 |
425 | // Rotate a vector in an angle
426 | function rotate([ax, ay], angle) {
427 | return [
428 | Math.cos(angle) * ax - Math.sin(angle) * ay,
429 | Math.sin(angle) * ax + Math.cos(angle) * ay];
430 | }
431 |
432 | function getRadianFromAngle(angle = 270) {
433 | return angle * Math.PI / 180;
434 | }
435 |
436 | // -- Conections between nodes --
437 | function connectPorts([nodeA, slotA], [nodeB, slotB]) {
438 | nodeA.ports[slotA] = [nodeB, slotB];
439 | nodeB.ports[slotB] = [nodeA, slotA];
440 | }
441 |
442 | function setInitialPositionForPivots(nodesArray) {
443 | for (var i = 0; i < nodesArray.length; i++) {
444 | for (var j = 0; j < 3; j++) {
445 | nodesArray[i].pivots[j] = nodes[i].getPortPosition(j);
446 | }
447 | }
448 | }
449 |
450 | function updatePivotsPosition(node) {
451 | for (var i = 0; i < 3; i++) {
452 | node.pivots[i] = node.getPortPosition(i);
453 | }
454 | }
455 |
456 |
457 | // ----- Drawing ------
458 | // Draw the initial node as a small circle
459 | function drawInitialNode(context, node) {
460 | context.beginPath();
461 | // context.arc(port0Position.x, port0Position.y, 5, 0, 2 * Math.PI);
462 | context.arc(node.position.x, node.position.y, 5, 0, 2 * Math.PI);
463 | if (node.label === 0) {
464 | context.fill();
465 | } else {
466 | context.fillStyle = 'white';
467 | context.fill();
468 | }
469 | context.stroke();
470 | }
471 |
472 | // Draw the shape of a triangle according to it's ports and it's connections
473 | function drawElements(context, node) {
474 | context.strokeStyle = 'black';
475 | context.fillStyle = 'black';
476 |
477 | if (node.label === 2) { // draws a black dot inside the triangle
478 | context.beginPath();
479 | context.arc(node.position.x, node.position.y, 3, 0, 2 * Math.PI);
480 | context.fill();
481 | context.stroke();
482 | }
483 |
484 | if (elementSelected) {
485 | // Highlight the selected element
486 | if (elementSelected[1] === node &&
487 | (elementSelected[0] === "node" || elementSelected[0] === "initialNode")) {
488 | context.strokeStyle = selectionColor;
489 | context.fillStyle = selectionColor;
490 | }
491 | }
492 |
493 | if (elementClicked) {
494 | if (elementClicked === node) {
495 | context.strokeStyle = selectionColor;
496 | context.fillStyle = selectionColor;
497 | }
498 | }
499 |
500 | if (node.label === 0 || node.label === 5) {
501 | drawInitialNode(context, node);
502 | } else {
503 | // -- Triangles --
504 | context.beginPath();
505 | // Port 0 to 1
506 | context.moveTo(node.getPortPosition(0).x, node.getPortPosition(0).y);
507 | context.lineTo(node.getPortPosition(1).x, node.getPortPosition(1).y);
508 |
509 | // Port 1 to 2
510 | context.moveTo(node.getPortPosition(1).x, node.getPortPosition(1).y);
511 | context.bezierCurveTo(node.getPortPosition(1).x, node.getPortPosition(1).y,
512 | node.position.x, node.position.y,
513 | node.getPortPosition(2).x, node.getPortPosition(2).y);
514 |
515 | // Port 2 to 0
516 | context.moveTo(node.getPortPosition(2).x, node.getPortPosition(2).y);
517 | context.lineTo(node.getPortPosition(0).x, node.getPortPosition(0).y);
518 |
519 | context.closePath();
520 | context.stroke();
521 | }
522 | // node.ports has the format of: [[node0, 0], [node1, 1], [node2, 2]]
523 | for (var i = 0; i < 3; i++) {
524 | var portPosition = node.getPortPosition(i);
525 | var portPivot = node.pivots[i];
526 |
527 | var nodeToConnect = node.ports[i][0];
528 | var slotToConnect = node.ports[i][1];
529 | if (nodeToConnect.label !== 0 && nodeToConnect.label !== 5) {
530 | var portToConnectPosition = nodeToConnect.getPortPosition(slotToConnect);
531 | var portToConnectPivot = nodeToConnect.pivots[slotToConnect];
532 | } else {
533 | var portToConnectPivot = nodeToConnect.position;
534 | var portToConnectPosition = nodeToConnect.position;
535 | }
536 |
537 | // -- Drawing pivots and lines --
538 | context.strokeStyle = 'black';
539 | context.fillStyle = 'black';
540 | if (node.label !== 0 && node.label !== 5) {
541 | // Create a line (curved, if it has a pivot) from the node beeing drawn and "nodeToConnect"
542 | context.beginPath();
543 | context.moveTo(portPosition.x, portPosition.y);
544 | context.bezierCurveTo(portPivot.x, portPivot.y,
545 | portToConnectPivot.x, portToConnectPivot.y,
546 | portToConnectPosition.x, portToConnectPosition.y);
547 | context.stroke();
548 | }
549 |
550 | // Highlight the selected pivot
551 | if (elementSelected) {
552 | if (elementSelected[1] === node && elementSelected[2] === i) { // Pivot on a selected node
553 | context.strokeStyle = selectionColor;
554 | context.fillStyle = selectionColor;
555 | }
556 | }
557 | // Shows the position of the pivots
558 | if (!hidePivots) {
559 | if (node.label !== 0 && node.label !== 5) { // Initial node does not shows the pivots
560 | context.beginPath();
561 | context.arc(portPivot.x, portPivot.y, 3, 0, 2 * Math.PI);
562 | context.fill();
563 | context.stroke();
564 | }
565 | }
566 |
567 | }
568 | }
569 |
570 |
--------------------------------------------------------------------------------
/nasic-render.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soonad/Formality-Net-Animator/3d9c370e921ae62bfea55808247ecf92b88bf4d7/nasic-render.gif
--------------------------------------------------------------------------------