├── 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 | 11 | 12 | 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 --------------------------------------------------------------------------------