├── Algorithms.js ├── BasicLib.js ├── CONCEPTUAL OUTLINE of this Framework.pdf ├── CSSobjectLib.js ├── CanvasObjects.js ├── EXAMPLE.zip ├── LICENSE ├── README.md ├── TO DO list ├── WebglFragmentShader.glsl └── WebglVertexShader.glsl /Algorithms.js: -------------------------------------------------------------------------------- 1 | //+++++++++++++++++++++++++++ 2 | //Author: Thomas Androxman 3 | //Date : Feb/2018 4 | //+++++++++++++++++++++++++++ 5 | //Contains: TypeNode, TypeEdge, TypeLinkedList, TypeDelaunayTriangulation, TypeHashGrid, TypePriorityQueue, TypeMazeGenerator, TypeRedBlackTree 6 | //DependsOn: BasicLib.js 7 | 8 | //Global Functions----------------------------------------------------------------------------------------------------------------------------------- 9 | 10 | //=================================================================================================================================================== 11 | //Classes / Constructor-functions 12 | //--------------------------------------------------------------------------------------------------------------------------------------------------- 13 | function TypeNode (newData) 14 | { //This is a generic object used in various graph data structures (linked lists, heaps, reb black trees, Prim, Dijkstra, etc) 15 | 16 | //PRIVATE properties 17 | var data = newData; //This can be anything. 18 | var auxVar; //An auxiliary variable for miscellaneous uses 19 | var edgesArr = []; //Array of references to other nodes 20 | var edgesWeightArr = []; //If the edges have weights, those weight values are stored here 21 | 22 | //PUBLIC Methods 23 | this.DeleteEdges = function () {edgesArr=[]; return this;} 24 | this.PushEdge = function (newEdge) {if (newNode!==void(0) && !(newNode instanceof TypeNode)) {return;} edgesArr.push(newNode); return this;} 25 | this.PopEdge = function () {return edgesArr.pop();} 26 | 27 | this.GetEdge = function (idx) {return edgesArr[idx];} 28 | this.GetEdgeWeight = function (idx) {return edgesWeightArrArr[idx];} 29 | this.GetData = function () {return data;} 30 | this.GetAuxVar = function () {return auxVar;} 31 | this.GetEdgeCount = function () {return edgesArr.length;} 32 | this.GetCopy = function () {return new TypeNode(data);} //Shallow copy of the node 33 | 34 | this.SetEdge = function (idx,newNode) {if (newNode!==void(0) && !(newNode instanceof TypeNode)) {return;} edgesArr[idx]=newNode;} 35 | this.SetEdgeWeight = function (idx,weight) {edgesWeightArr[idx]=weight;} 36 | this.SetData = function (newData) {data = newData;} 37 | this.SetAuxVar = function (something) {auxVar = something;} 38 | this.SetEqualTo = function (otherNode) 39 | { 40 | if (!(otherNode instanceof TypeNode)) {return;} 41 | data = otherNode.GetData(); 42 | edgesArr = []; 43 | 44 | var otherEdgeCount = otherNode.GetEdgeCount(); 45 | for (let i=0; ilength-1) {Say('WARNING: (CheckRangeVal) Start index is out of range',-1); return;} //idx1 should not be as tolerant as idx2 126 | if (isNaN(idx2) || idx2=length) {result.index2=length-1;} //Clip the ending index. 127 | 128 | return result; 129 | } 130 | var Populate = function (sourceList,idx1,idx2,deleteFromSource) 131 | { //Helper method that adds initial nodes into the list 132 | 133 | //Argument gate 134 | var sourceListSize=0; 135 | if (!(sourceList instanceof TypeLinkedList)) {return;} else {sourceListSize = sourceList.Length();} if (sourceListSize==0) {return;} 136 | var cleanIdx = CleanRangeVal(idx1,idx2,sourceListSize); 137 | if (!cleanIdx) {return;} else {idx1 = cleanIdx.index1; idx2 = cleanIdx.index2;} 138 | 139 | //Update the size of the new list 140 | size = idx2 - idx1 + 1; 141 | 142 | if (deleteFromSource) 143 | { 144 | let deletedSequence = sourceList.Delete(idx1,idx2); 145 | startNode = deletedSequence.startNode; 146 | endNode = deletedSequence.endNode; 147 | bookmark.node = startNode; 148 | bookmark.index = 0; 149 | return; 150 | } 151 | 152 | //Shallow-copy nodes from source list 153 | var newSeqStart; 154 | var newSeqEnd; 155 | for (let i=idx1; i<=idx2; i++) 156 | { 157 | let clonedNode = new TypeNode(sourceList.Get(i)); 158 | 159 | if (i==idx1) {newSeqStart = newSeqEnd = clonedNode; continue;} 160 | clonedNode.SetEdge(0,newSeqEnd); 161 | newSeqEnd.SetEdge(1,clonedNode); 162 | newSeqEnd = clonedNode; 163 | } 164 | startNode = newSeqStart; 165 | endNode = newSeqEnd; 166 | bookmark.node = newSeqStart; 167 | bookmark.index = 0; 168 | } 169 | var MoveBookmarkTo = function (idx) 170 | { //Positions the bookmark onto the specified node 171 | //Note: When the list is not empty, the bookmark.node object is guaranteed to be initialized 172 | 173 | //Argument gate 174 | if (size==0) {return;} //Nothing to do 175 | if (idx<0) {idx=0;} else if (idx>=size) {idx=size-1;} //clip the idx to the list's current range 176 | 177 | //Determine if either of the list ends is closer than the bookmark 178 | var travelDelta = idx - bookmark.index; //Distance the bookmark will have to travel 179 | if (idx==0 || idxsize-1) {continue;} 210 | let idx3 = j+2*segmentWidth-1; if (idx3>size-1) {idx3=size-1;} 211 | MergeTwoSortedSegments(j,idx2,idx3,compFunction); 212 | } 213 | } 214 | } 215 | var MergeTwoSortedSegments = function (idx1,idx2,idx3,compFunction) 216 | { //Merge two contiguous sorted segments of the list in a sorted way 217 | 218 | if (idx1==idx2 && idx1==idx3) {return;} //Trivial case 219 | 220 | //Put a finger on idx1 and a finger on idx2 221 | //Note: finger1 and finger2 have the node reference in them in order to avoid using the bookmark (bouncing back and forth in the list during the operation) 222 | var finger1 = {node:MoveBookmarkTo(idx1), index:idx1}; 223 | var finger2 = {node:MoveBookmarkTo(idx2), index:idx2}; 224 | 225 | //Compare the two values at fingers 226 | //Move the smaller value of the two behind the node initially at idx1 227 | while (finger1.index=size || isAfter)?true:false); 420 | 421 | //Update the bookmark 422 | //Note: The bookmark points to the final insertion point 423 | //Note: The bookmark.index needs to be adjusted by one in some cases 424 | bookmark.node = thisNode; 425 | bookmark.index = (!isAfter && idx1idx2)? bookmark.index+1 : bookmark.index; 426 | } 427 | 428 | 429 | this.Get = function (idx) {return (idx>=0 && idx Default values in case of bad argument input 574 | var population; //Holds the total number of objects in all grids combined 575 | var gridBoundary; //Object {max:maxDimension, min:minDimension, span:measurements} 576 | var gridSubdivisions; //A TypeXYZw holding the integer number of subdivions in each dimension 577 | var gridCellDim; //A TypeXYZw holding the dimensions of a cell 578 | 579 | var coreGridArray; //An array of all the grid cells (each cell will hold a linked list of data) 580 | 581 | //Private methods 582 | var Initialize = function (subD, maxP, minP) 583 | { 584 | population = 0; 585 | defaultDim = NewDimensions (new TypeXYZw(1,1,1), new TypeXYZw()); 586 | 587 | //Argument gate 588 | if (!(subD instanceof TypeXYZw)) {subD = new TypeXYZw(subD);} 589 | if (!(maxP instanceof TypeXYZw)) {maxP = new TypeXYZw(maxP);} 590 | if (!(minP instanceof TypeXYZw)) {minP = new TypeXYZw(minP);} 591 | if (minP.IsEqual(maxP)) {gridBoundary = defaultDim; Say('WARNING: (ChangeDimensions) The received max point is equal to the min point (defaults were applied; grid must have some size)',-1); return;} 592 | if (minP.x>maxP.x || minP.y>maxP.y || minP.z>maxP.z) {gridBoundary = defaultDim; Say('WARNING: (ChangeDimensions) All components of the min point must be smaller than the max point (defaults were applied; grid must have some size)',-1); return;} 593 | 594 | subD.x = (Number.isNaN(subD.x) || subD.x<1) ? 1 : Math.floor(subD.x); 595 | subD.y = (Number.isNaN(subD.y) || subD.y<1) ? 1 : Math.floor(subD.y); 596 | subD.z = (Number.isNaN(subD.z) || subD.z<1) ? 1 : Math.floor(subD.z); 597 | 598 | gridBoundary = NewDimensions(maxP, minP); 599 | gridSubdivisions = subD; 600 | gridCellDim = new TypeXYZw(gridBoundary.span.x/gridSubdivisions.x, gridBoundary.span.y/gridSubdivisions.y, gridBoundary.span.z/gridSubdivisions.z); 601 | coreGridArray = NewCoreArray(); 602 | } 603 | var NewDimensions = function (maxP,minP) {return {min:minP,max:maxP,span:maxP.Minus(minP)};} 604 | var NewCoreArray = function () 605 | { 606 | //Start the core array 607 | var newArr = new Array(gridSubdivisions.z); //A row of planes 608 | 609 | //Create linkedList objects for each cell 610 | for (let k=0; kgridBoundary.max.x || testPos.y>gridBoundary.max.y || testPos.z>gridBoundary.max.z) {Say('WARNING: (CheckBounds) Out of bounds',-1); return false;} 626 | else {return true}; 627 | } 628 | var GridCoordinates = function (wPos) 629 | { 630 | var delta = wPos.Minus(gridBoundary.min); 631 | var gridX = (wPos.x>=gridBoundary.max.x)? gridSubdivisions.x-1 : (wPos.x<=gridBoundary.min.x || gridCellDim.x<=0)? 0 : Math.floor(delta.x/gridCellDim.x); 632 | var gridY = (wPos.y>=gridBoundary.max.y)? gridSubdivisions.y-1 : (wPos.y<=gridBoundary.min.y || gridCellDim.y<=0)? 0 : Math.floor(delta.y/gridCellDim.y); 633 | var gridZ = (wPos.z>=gridBoundary.max.z)? gridSubdivisions.z-1 : (wPos.z<=gridBoundary.min.z || gridCellDim.z<=0)? 0 : Math.floor(delta.z/gridCellDim.z); 634 | return new TypeXYZw(gridX, gridY, gridZ); 635 | } 636 | 637 | //Public methods 638 | this.Clear = function () {population = 0; for (let k=0; k, , \n'; 695 | result += 'Dimensions : , , \n'; 696 | result += 'Min point : '+gridBoundary.min+'\n'; 697 | result += 'Max point : '+gridBoundary.max+'\n'; 698 | result += 'Grid Cell : , , '; 699 | return result; 700 | } 701 | //Initialization 702 | Initialize(subD, maxP, minP); 703 | } 704 | //--------------------------------------------------------------------------------------------------------------------------------------------------- 705 | function TypePriorityQueue (isMax, compareFunction) 706 | { //A Heap object (implemented as an array) which is acting as a priority queue 707 | 708 | //Private properties 709 | var heapArr; //Array: The heap is stored in this array 710 | var compFunct; //Reference to a function: if present this will be used for comparisons between heap items 711 | var isMaxHeap; //Boolean 712 | 713 | //Private methods 714 | var Initialize = function (isMax, compareFunction) 715 | { 716 | heapArr = []; 717 | isMaxHeap = (isMax==true)? true : false; 718 | compFunct = compareFunction; 719 | } 720 | var Compare = function (a, b) {return (compFunct)? compFunct(a,b) : (a==b)? 0 : (a>b)? 1 : -1;} //Can also handle strings 721 | var BuildHeap = function () 722 | { //We can use the Heapify method in a bottom-up manner to convert an array into a max(min) heap 723 | 724 | //Note: the parent of element heapArr[length-1] is at heapArr[length/2-1] 725 | //Note: so all elements from 0 to length/2 -1 need to be checked from the bottom up 726 | var heapLen = heapArr.length; if (heapLen==0) {return;} 727 | var fromIdx = Math.floor(heapLen/2); 728 | for (let i=fromIdx; i>=0; i--) {Heapify(i);} 729 | } 730 | var Heapify = function (startIdx,endIdx) 731 | { //HEAPIFY assumes that the binary trees rooted at LEFT(i) and RIGHT(i) are max (or min) heaps, but that A[i] might be smaller than its children, thus violating the max(min) heap property. 732 | //HEAPIFY lets the value at A[i] “float down” in the max(min) heap so that the subtree rooted at index i obeys the max(min) heap property. 733 | 734 | var lastIdx = (endIdx!==void(0))? endIdx : heapArr.length-1; 735 | var lastParent = Math.floor((lastIdx-1)/2); 736 | var flip = (isMaxHeap)? 1 : -1; 737 | 738 | if(startIdx===void(0) || startIdx<0) {startIdx=0;} 739 | 740 | while (startIdx<=lastIdx) 741 | { 742 | let choiceIdx = startIdx; //Holds the index with the largest item (left or right) 743 | let leftIdx = (startIdx+1)*2 - 1; //Start of the left tree in the array 744 | let rightIdx = (startIdx+1)*2; //Start of the right tree in the array 745 | 746 | //essentially we are going to compare three elements and choose the index of the largest/smallest element 747 | if (leftIdx>lastIdx) {break;} //The last row may be incomplete (say half way) so if there is no leftIdx, then there is no rightIdx either and we have nothing to do 748 | if (rightIdx<=lastIdx && Compare(heapArr[rightIdx],heapArr[choiceIdx])*flip>0) {choiceIdx=rightIdx;} //Compare parent to right 749 | if (leftIdx <=lastIdx && Compare(heapArr[ leftIdx],heapArr[choiceIdx])*flip>0) {choiceIdx=leftIdx;} //Compare parent(or right) to left 750 | if (choiceIdx==startIdx) {break;} //If the parent survived the comparison, no need to proceed (assume the rest of the branch is heapified) 751 | 752 | //Swap 753 | let temp = heapArr[choiceIdx]; heapArr[choiceIdx] = heapArr[startIdx]; heapArr[startIdx] = temp; 754 | startIdx = choiceIdx; 755 | } 756 | } 757 | var InvHeapify = function (idx) 758 | { //Moves up, from idx to 1 in the heapArr (working in reverse direction compared to Heapify) 759 | 760 | var lastIdx = heapArr.length-1; if (idx===void(0)) {idx = lastIdx;} 761 | var flip = (isMaxHeap)? 1 : -1; 762 | 763 | while (idx>0) 764 | { 765 | let parentIdx = Math.floor((idx-1)/2); //Should give the same result regardless if idx is left or right child 766 | let heapProperty = (Compare(heapArr[parentIdx],heapArr[idx])*flip>=0)? true : false; 767 | //Swap parent and idx if the heap property is not preserved 768 | if (!heapProperty) {let temp = heapArr[parentIdx]; heapArr[parentIdx] = heapArr[idx]; heapArr[idx] = temp;} //Swap 769 | //Setup for the next iteration 770 | idx = parentIdx; 771 | } 772 | } 773 | var CheckHeapPropertyAt = function (idx) 774 | { //Check the value at idx to see if it still satisfies the heap property 775 | //The value at idx might have changed and we don't know how (we are only given a compare function to work with) 776 | 777 | if (idx===void(0)) {return;} 778 | 779 | var flip = (isMaxHeap)? 1 : -1; 780 | var data = heapArr[idx]; //This data might have changed from its orginal value 781 | var parentIdx = Math.floor((idx-1)/2); 782 | var leftIdx = (idx+1)*2 - 1; 783 | var rightIdx = (idx+1)*2; 784 | var lastIdx = heapArr.length; 785 | 786 | //idx is sanwiched between the parent and the children. Check to see if the proportions are correct 787 | var parentIsCorrectSize = (idx<=0 || Compare(heapArr[parentIdx],heapArr[idx])*flip>0)? true : false; 788 | var childIsCorrectSize = (leftIdx>lastIdx || Compare(heapArr[idx],heapArr[leftIdx])*flip>0 && Compare(heapArr[idx],heapArr[rightIdx])*flip>0)? true : false; 789 | if ( parentIsCorrectSize && !childIsCorrectSize) {Heapify(idx); return;} //trickling down into the heap 790 | if (!parentIsCorrectSize && childIsCorrectSize) {InvHeapify(idx); return;} //percolating upwards in the heap 791 | } 792 | var InsertOne = function (data) 793 | { //Inserts one item into the heap and restores the heap property 794 | 795 | heapArr.push(data); //item is added at the end of the heap array 796 | InvHeapify(); 797 | } 798 | var PrintHeap = function (splitTheLevels) 799 | { 800 | var result = ''; 801 | var count = heapArr.length; 802 | for (let i=0; i will convert single data into an array regardless 823 | 824 | if (data===void(0)){return;} 825 | if (asData || !IsArray(data)) {data = [data];} else if (data.length==0) {return;} 826 | if (heapArr.length==0) {heapArr = data.slice(); BuildHeap(); return;} //Makes a shallow copy of the source array 827 | 828 | //If we are here, the heapArr already contains values and new values must be added to it one by one 829 | var len = data.length; 830 | for (let i=0; i0; i--) 850 | { //This loop will sort the heapArr in the oposite order (a min heap is sorted with the largest item at the front) 851 | //The top item is guaranteed min(or max). 852 | //Put the top item at the bottom -> swap heapArr[1] with heapArr[i] 853 | let temp = heapArr[0]; heapArr[0]=heapArr[i]; heapArr[i]=temp; 854 | Heapify(0,i-1); 855 | } 856 | //Reverse the sorting 857 | //Note: a sorted maxHeap will result in an array with the minimum element at the front. The sorted array needs to be reversed to maintain the max heap condition 858 | for (let i=0; i<=halfIdx; i++) {temp=heapArr[i]; heapArr[i]=heapArr[lastIdx-i]; heapArr[lastIdx-i]=temp;} 859 | } 860 | this.toString = function () 861 | { 862 | var result = '[Object TypeHeap]\n'; 863 | result += 'isMaxHeap : '+isMaxHeap+'\n'; 864 | result += 'Heap length : '+heapArr.length+'\n'; 865 | result += '---------------------------------------\n' 866 | result += 'Data :' 867 | result += PrintHeap()+'\n'; 868 | result += '---------------------------------------' 869 | return result; 870 | } 871 | 872 | //Initialization 873 | Initialize(isMax, compareFunction); 874 | } 875 | //--------------------------------------------------------------------------------------------------------------------------------------------------- 876 | function TypeMazeGenerator (dim) 877 | { //This object generates a maze out of a grid 878 | //Grid size is given by the dimentions object 879 | //The algorithm is based on Prim's concept 880 | 881 | //Private properties 882 | var mazeDim; //A typeXYZw for the count of cells in the x,y (and z) dimensions of the maze 883 | var startingCell; //A TypeXYZw for array coordinates of the beginning of the maze 884 | var endingCell; //A TypeXYZw for array coordinates of the ending of the maze 885 | var mazeArr; //A grid that holds the maze information. When a cell is non-empty it is registered as part of the maze (it is part of the Prim spanning tree) 886 | var wallPatternArr; //An array containing the rows-wall-pattern and columns-wall-pattern 887 | 888 | //Private methods 889 | var Initialize = function (dim) 890 | { 891 | ChangeDimensions(dim); 892 | } 893 | var ChangeDimensions = function (newDim) 894 | { 895 | if (newDim===void(0)) {return;} 896 | newDim = new TypeXYZw(newDim); newDim.SetInt(); 897 | 898 | if (newDim.x == 0 || newDim.y == 0) {Say('WARNING: (ChangeDimansions) The maze must have non zero X and Y dimensions',-1); return;} 899 | if (newDim.z == 0) {newDim.z=1;} //Most times the Z is left out but is implied to be 1. (The TypeXYZw initializes z = 0) 900 | 901 | mazeDim = newDim; 902 | 903 | AssignStartEnd(); 904 | GenerateGrid(); 905 | GenerateMaze(); 906 | GenerateWallPattern(); 907 | 908 | return true; 909 | } 910 | var AssignStartEnd = function () 911 | { 912 | //The starting cell 913 | startingCell = new TypeXYZw(0,0,0); 914 | 915 | //The ending cell 916 | var isExitAlongX = (Math.random()>0.5)? true : false; 917 | var exitXcoord = ( isExitAlongX)? mazeDim.x : Math.floor(Math.random()*(mazeDim.x-1)); 918 | var exitYcoord = (!isExitAlongX)? mazeDim.y : Math.floor(Math.random()*(mazeDim.y-1)); 919 | var exitZcoord = Math.floor(Math.random()*(mazeDim.z-1)); 920 | endingCell = new TypeXYZw(exitXcoord,exitYcoord,exitZcoord); //The ending cell is outside the mazeArr 921 | } 922 | var GenerateGrid = function () 923 | { //Creates an empty array of size 'mazeDim' 924 | 925 | mazeArr = []; //Reset the array 926 | 927 | for (let k=0; k void(0) it is considered unvisited 938 | //Note: When a cell contains a TypeXYZw object (coordinates of parent) it is considered visited (part of Prim) 939 | //Note: In Prim's algorithm (minimum spanning tree) a node can have many edges, but it can only have ONE PARENT. As such, each cell stores the direction to its parent 940 | 941 | var queueCompare = function (a,b) {return a.weight-b.weight;} //Priority queue will compare the 'weight' property of the object generated by 'NewQueueItem' 942 | var minQueue = new TypePriorityQueue(false,queueCompare); //This min priority queue will be used to hold proposals (objects containing adjacent 'frontier' cells and random weights) 943 | 944 | //The starting cell has no parent direction -> parent coordintas are NaN. 945 | //The queue holds an object with preperties of -> cellCoords, parentCoords, and a weight value 946 | minQueue.Push(NewQueueItem(startingCell,new TypeXYZw(NaN,NaN,NaN),0)); 947 | 948 | while (!minQueue.IsEmpty()) 949 | { 950 | let oneQueueItem; //Think of a queueItem as a "proposal". An object with properties -> thisCellCoord, parentCellCoord, w 951 | let currCellData; //The value in the mazeArr where thisCellCoord is pointing 952 | 953 | //Pop a frontier queueItem 954 | //Note: A queue item is considerred frontier if its mazeArr cell value is empty (otherwise it is a Prim cell) 955 | //Note: It is possible that a queueItem pushed earlier has now become Prim, but it is still in the queue 956 | //Note: Prim cells will be popped then pop again until a non Prim cell is at hand 957 | do { oneQueueItem = minQueue.Pop(); currCellData = (oneQueueItem)? mazeArr[oneQueueItem.coord.z][oneQueueItem.coord.y][oneQueueItem.coord.x] : NaN;} while (currCellData) 958 | 959 | if (!oneQueueItem) {return;} //if the queue became empty while looking for an item then we are done 960 | 961 | //Register the queueItem on the mazeArr as Prim 962 | mazeArr[oneQueueItem.coord.z][oneQueueItem.coord.y][oneQueueItem.coord.x] = oneQueueItem.parentCoord; //if an array cell is not empty then it is part of the Prim maze 963 | 964 | //Push adjacent cells into the queue 965 | PushAdjacentCells(oneQueueItem, minQueue); //Push the coordinates of the available adjacent cells 966 | } 967 | 968 | } 969 | var GenerateWallPattern = function () 970 | { 971 | //The wall pattern of each row/column is stored as an array of bits (1 or 0) for yes-wall or no-wall 972 | /* Example wall pattern is 2D (X,Y) 973 | LEVEL 974 | [ [[1,0,0,1,0,1,0,1,0], //array of vertical-walls rows -> || 975 | [1,0,1,0,1,0,1,1,1], //Note: there are 9 walls for 8 horCells 976 | [1,0,0,1,1,0,1,0,1], //Note: one row of this array corresponds to a row of cells in the maze 977 | [1,0,1,0,0,1,0,0,1], 978 | [1,1,1,1,0,1,1,1,1], 979 | [1,0,1,0,1,0,1,0,1]], //Array has 6 horRows -> if (Arr[horRow][column] == 1) {vertical wall exists} 980 | 981 | [[1,0,1,0,1,0,1], //array of horizontal-walls columns -> = 982 | [1,0,1,1,0,0,1], //Note: There are 7 walls for 6 vertCells 983 | [1,0,1,0,0,0,1], //Note: one row of this array corresponds to a column of cells in the maze 984 | [1,0,0,0,1,0,1], 985 | [1,1,0,1,1,0,1], 986 | [1,0,1,0,0,0,1], 987 | [1,0,0,1,0,1,1], 988 | [1,0,0,1,0,0,1]] //Array has 8 vertRows -> if (Arr[vertRow][column] == 1) {horizontal wall exists} 989 | ]; //Full level Array 990 | 991 | CAPS -> The front/back faces (3D maze) 992 | [ [1,1,1,1,1,1,1,1], //One row of caps corresponds to one row of cells in the maze 993 | [1,1,1,1,1,1,1,1], 994 | [1,1,1,1,1,1,1,1], 995 | [1,1,1,1,1,1,1,1], 996 | [1,1,1,1,1,1,1,1], 997 | [1,1,1,1,1,1,1,1] 998 | ] //Array of caps 999 | 1000 | If the maze is 3D there are multiple Level arrays and cap arrays. Example Array structure 1001 | 1002 | CompleteMazeWallPatternArr = [LevelsArr,CapsArr]; 1003 | LevelsArr = [level1Arr,level2Arr,level3Arr, ...]; 1004 | level1Arr = [vertWallsArr,HorWallsArr] 1005 | vertWallsArr = [row1Arr, row2Arr, ...]; 1006 | horWallsArr = [col1Arr, col2Arr, ...]; 1007 | CapsArr = [level1CapArr,level2CapArr, ...] 1008 | level1CapArr = [row1Arr, row2Arr, ...] 1009 | */ 1010 | 1011 | var capsArr = []; 1012 | var levelsArr = []; 1013 | var oneLevelCaps = []; 1014 | var nextLevelCaps = []; 1015 | for (let mazeZ=0; mazeZmazeDim.x-1) {wallPatternArr[0][endingCell.z][0][endingCell.y][mazeDim.x]=false;} else if (endingCell.x<0) {wallPatternArr[0][endingCell.z][0][endingCell.y][0]=false;} 1073 | if (endingCell.y>mazeDim.y-1) {wallPatternArr[0][endingCell.z][1][endingCell.x][mazeDim.y]=false;} else if (endingCell.y<0) {wallPatternArr[0][endingCell.z][1][endingCell.x][0]=false;} 1074 | if (endingCell.z>mazeDim.z-1) {wallPatternArr[1][mazeDim.z][endingCell.y][mazeDim.x]=false;} else if (endingCell.z<0) {wallPatternArr[1][0][endingCell.y][endingCell.x]=false;} 1075 | } 1076 | var NewQueueItem = function (thisCell, parentCell, w) {return {coord:thisCell, parentCoord:parentCell, weight:w}; } 1077 | var PushAdjacentCells = function (sourceQueueItem, minQueue) 1078 | { //Find non visitied (non Prim) adjacent cells and push them into the queue 1079 | //Note: The sourceQueueItem becomes the parent 1080 | 1081 | //Get the coordinates of the six possible adjacent cells 1082 | var currentCoord = sourceQueueItem.coord; 1083 | var xNegCoord = (currentCoord.x>0)? new TypeXYZw(currentCoord.x-1,currentCoord.y,currentCoord.z) : void(0); 1084 | var xPosCoord = (currentCoord.x0)? new TypeXYZw(currentCoord.x,currentCoord.y-1,currentCoord.z) : void(0); 1086 | var yPosCoord = (currentCoord.y0)? new TypeXYZw(currentCoord.x,currentCoord.y,currentCoord.z-1) : void(0); 1088 | var zPosCoord = (currentCoord.z '; 1117 | } 1118 | result += '\n'; 1119 | } 1120 | result += '\n'; 1121 | } 1122 | return result; 1123 | } 1124 | var PrintWallPattern = function () 1125 | { 1126 | //|¯|¯|¯|¯|¯| 1127 | //|¯|¯|¯|¯|¯| 1128 | // ¯ ¯ ¯ ¯ ¯ 1129 | 1130 | var result = ''; 1131 | for (mazeZ=0; mazeZ<=mazeDim.z; mazeZ++) 1132 | { 1133 | let levelWalls = ''; 1134 | let levelCaps = ''; 1135 | let oneLevelCapsArr = wallPatternArr[1][mazeZ]; 1136 | let oneLevelWallArr = wallPatternArr[0][mazeZ]; 1137 | for (mazeY=0; mazeY<=mazeDim.y; mazeY++) 1138 | { 1139 | for (mazeX=0; mazeX<=mazeDim.x; mazeX++) 1140 | { 1141 | //Special cases 1142 | if (mazeZ< mazeDim.z && mazeY< mazeDim.y && mazeX==mazeDim.x) {levelWalls += (oneLevelWallArr[0][mazeY][mazeX])? '|\n' : ' \n'; continue;} //One more vertical wall at the end 1143 | if (mazeZ< mazeDim.z && mazeX< mazeDim.x && mazeY==mazeDim.y) {levelWalls += (oneLevelWallArr[1][mazeX][mazeY])? ' '+String.fromCharCode(8254) : ' '; continue;} //One more horizontal wall at the end 1144 | if (mazeX< mazeDim.x && mazeY< mazeDim.y && mazeZ==mazeDim.z) {levelCaps += (oneLevelCapsArr[mazeY][mazeX])? ' x' : ' o'; if(mazeX==mazeDim.x-1) {levelCaps += '\n';} continue;} //One more cap layer at the end 1145 | if (mazeX==mazeDim.x || mazeY==mazeDim.y) {levelWalls += '\n'; continue;} 1146 | 1147 | //vertical walls 1148 | levelWalls += (oneLevelWallArr[0][mazeY][mazeX])? '|' : ' '; 1149 | 1150 | //horizontal walls 1151 | levelWalls += (oneLevelWallArr[1][mazeX][mazeY])? String.fromCharCode(8254) : ' '; //String.fromCharCode(8254) --> overbar 1152 | 1153 | //The caps 1154 | levelCaps += (oneLevelCapsArr[mazeY][mazeX])? ' x' : ' o'; 1155 | if(mazeX==mazeDim.x-1) {levelCaps += '\n';} //New line 1156 | } 1157 | } 1158 | result += 'Level '+mazeZ+'\n'; 1159 | result += (mazeZ1) {thisNode.GetAuxVar().counter--; population--; return true;} 1231 | 1232 | var doFix = (thisNode.GetAuxVar().color=='black')? true : false; 1233 | var successorNode = nilNode; 1234 | successorNode.SetEdge(2, thisNode.GetEdge(2)); //Temporarily set the nilNode's parent to thisNode parent 1235 | 1236 | if (thisNode.GetEdge(0)==nilNode || thisNode.GetEdge(1)==nilNode) 1237 | { //Case 1: (trivial) thisNode has no left or right child (or neither) 1238 | if (thisNode.GetEdge(0)==nilNode) {successorNode = thisNode.GetEdge(1); successorNode.SetEdge(2, thisNode.GetEdge(2));} 1239 | if (thisNode.GetEdge(1)==nilNode) {successorNode = thisNode.GetEdge(0); successorNode.SetEdge(2, thisNode.GetEdge(2));} 1240 | 1241 | if (thisNode.GetEdge(2)==nilNode) {headNode = successorNode;} //Just deleted the head itself (no children) 1242 | else if (thisNode.GetEdge(2).GetEdge(0)==thisNode) {thisNode.GetEdge(2).SetEdge(0, successorNode);} //The deleted was a left leaf 1243 | else {thisNode.GetEdge(2).SetEdge(1, successorNode);} //The deleted was a right leaf 1244 | } 1245 | else 1246 | { //Case2: Non trivial. The successor will be the minimum node on thisNode right subtree 1247 | successorNode = TreeMin (thisNode.GetEdge(1)); 1248 | doFix = (successorNode.GetAuxVar().color=='black')? true : false; 1249 | 1250 | if (successorNode==thisNode.GetEdge(1)) 1251 | { //the successor happens to be the immediate right node 1252 | BypassNode (thisNode,successorNode); 1253 | successorNode.GetAuxVar().color = thisNode.GetAuxVar().color; 1254 | successorNode.GetEdge(1).SetEdge(2, successorNode); 1255 | successorNode = successorNode.GetEdge(1); //the right child of the successor is the one to actually be passed for fix 1256 | } 1257 | else 1258 | { 1259 | let x = successorNode.GetEdge(1); 1260 | successorNode.GetEdge(2).SetEdge(0, successorNode.GetEdge(1)); 1261 | successorNode.GetEdge(1).SetEdge(2, successorNode.GetEdge(2)); 1262 | successorNode.SetEdge(1, thisNode.GetEdge(1)); 1263 | thisNode.GetEdge(1).SetEdge(2, successorNode); 1264 | BypassNode (thisNode,successorNode); 1265 | successorNode.GetAuxVar().color = thisNode.GetAuxVar().color; 1266 | successorNode = x; 1267 | } 1268 | } 1269 | 1270 | if (doFix) {DeletionRepair(successorNode);} 1271 | population--; 1272 | return true; 1273 | } 1274 | var DeletionRepair = function (successorNode) 1275 | { //Rebalances the tree when a node is freshly deleted 1276 | //Note: This is a helper method for --> DeleteOne 1277 | //Note: Pseudocode page 326 and Fig13.7 of CLR book 1278 | 1279 | while (successorNode!=headNode && successorNode.GetAuxVar().color=='black') //void(0) is by definition black 1280 | { 1281 | if (successorNode==successorNode.GetEdge(2).GetEdge(0)) 1282 | { //SuccessorNode is a left child 1283 | let brotherNode = successorNode.GetEdge(2).GetEdge(1); 1284 | if (brotherNode.GetAuxVar().color=='red') 1285 | { 1286 | brotherNode.GetAuxVar().color='black'; 1287 | successorNode.GetEdge(2).GetAuxVar().color = 'red'; 1288 | RotateLeft(successorNode.GetEdge(2)); 1289 | brotherNode = successorNode.GetEdge(2).GetEdge(1); 1290 | } 1291 | if (brotherNode.GetEdge(1).GetAuxVar().color=='black' && brotherNode.GetEdge(0).GetAuxVar().color=='black') 1292 | { 1293 | brotherNode.GetAuxVar().color='red'; 1294 | successorNode = successorNode.GetEdge(2); 1295 | } 1296 | else if (brotherNode.GetEdge(1).GetAuxVar().color=='black') 1297 | { 1298 | brotherNode.GetEdge(0).GetAuxVar().color = 'black'; 1299 | brotherNode.GetAuxVar().color='red'; 1300 | RotateRight(brotherNode); 1301 | brotherNode=successorNode.GetEdge(2).GetEdge(1); 1302 | 1303 | brotherNode.GetAuxVar().color = successorNode.GetEdge(2).GetAuxVar().color; 1304 | successorNode.GetEdge(2).GetAuxVar().color = 'black'; 1305 | brotherNode.GetEdge(1).GetAuxVar().color = 'black'; 1306 | RotateLeft(successorNode.GetEdge(2)); 1307 | successorNode=headNode; 1308 | } 1309 | else 1310 | { 1311 | brotherNode.GetAuxVar().color = successorNode.GetEdge(2).GetAuxVar().color; 1312 | successorNode.GetEdge(2).GetAuxVar().color = 'black'; 1313 | brotherNode.GetEdge(1).GetAuxVar().color = 'black'; 1314 | RotateLeft(successorNode.GetEdge(2)); 1315 | successorNode = headNode; 1316 | } 1317 | } 1318 | else 1319 | { //It is the right child 1320 | let brotherNode = successorNode.GetEdge(2).GetEdge(0); 1321 | 1322 | if (brotherNode.GetAuxVar().color=='red') 1323 | { 1324 | brotherNode.GetAuxVar().color='black'; 1325 | successorNode.GetEdge(2).GetAuxVar().color = 'red'; 1326 | RotateRight(successorNode.GetEdge(2)); 1327 | brotherNode = successorNode.GetEdge(2).GetEdge(0); 1328 | } 1329 | 1330 | if (brotherNode.GetEdge(1).GetAuxVar().color=='black' && brotherNode.GetEdge(0).GetAuxVar().color=='black') 1331 | { 1332 | brotherNode.GetAuxVar().color='red'; 1333 | successorNode = successorNode.GetEdge(2); 1334 | } 1335 | else if (brotherNode.GetEdge(0).GetAuxVar().color=='black') 1336 | { 1337 | brotherNode.GetEdge(1).GetAuxVar().color = 'black'; 1338 | brotherNode.GetAuxVar().color='red'; 1339 | RotateLeft(brotherNode); 1340 | brotherNode=successorNode.GetEdge(2).GetEdge(0); 1341 | 1342 | brotherNode.GetAuxVar().color = successorNode.GetEdge(2).GetAuxVar().color; 1343 | successorNode.GetEdge(2).GetAuxVar().color = 'black'; 1344 | brotherNode.GetEdge(0).GetAuxVar().color = 'black'; 1345 | RotateRight(successorNode.GetEdge(2)); 1346 | successorNode=headNode; 1347 | } 1348 | else 1349 | { 1350 | brotherNode.GetAuxVar().color = successorNode.GetEdge(2).GetAuxVar().color; 1351 | successorNode.GetEdge(2).GetAuxVar().color = 'black'; 1352 | brotherNode.GetEdge(0).GetAuxVar().color = 'black'; 1353 | RotateRight(successorNode.GetEdge(2)); 1354 | successorNode = headNode; 1355 | } 1356 | } 1357 | } 1358 | successorNode.GetAuxVar().color = 'black'; 1359 | } 1360 | var AddOne = function (data) 1361 | { //Add one node into the tree 1362 | //Note: In this red black tree version, multiple instances of the same data simply increment a node counter 1363 | 1364 | var newNode = new TypeNode(data); 1365 | 1366 | newNode.SetEdge(0, nilNode); //Left child 1367 | newNode.SetEdge(1, nilNode); //Right child 1368 | newNode.SetEdge(2, nilNode); //Parent 1369 | newNode.SetAuxVar(NewAuxVariable(1,'red')); 1370 | population++; 1371 | 1372 | //Trivial case: the tree is empty 1373 | if (headNode==nilNode) {headNode = newNode; headNode.GetAuxVar().color = 'black'; return true;} 1374 | 1375 | //Insert into the tree somewhere 1376 | var currentNode = headNode; 1377 | while (currentNode!=nilNode) 1378 | { //Start from the top and traverse the tree until the correct spot is found 1379 | let comparison = Compare(data,currentNode.GetData()); 1380 | if (comparison == 0) {currentNode.GetAuxVar().counter++; return true;} //Identical data simply increments counter 1381 | if (comparison < 0 && currentNode.GetEdge(0)!=nilNode) {currentNode = currentNode.GetEdge(0); continue;} //Walk left and continue searching 1382 | if (comparison < 0 && currentNode.GetEdge(0)==nilNode) {currentNode.SetEdge(0, newNode); newNode.SetEdge(2, currentNode); break;} //Add node here and stop searching 1383 | if (comparison > 0 && currentNode.GetEdge(1)!=nilNode) {currentNode = currentNode.GetEdge(1); continue;} //Walk right and continue searching 1384 | if (comparison > 0 && currentNode.GetEdge(1)==nilNode) {currentNode.SetEdge(1, newNode); newNode.SetEdge(2, currentNode); break;} //Add node here and stop searching 1385 | 1386 | break; //if structure should never reach down here 1387 | } 1388 | AdditionRepair(newNode); 1389 | return true; 1390 | } 1391 | var AdditionRepair = function (currentNode) 1392 | { //Rebalances the tree when a node is freshly added 1393 | //Note: This is a helper method for --> AddOne 1394 | //Note: Pseudocode page 316 and Fig13.4 of CLR book 1395 | 1396 | //Trivial checks 1397 | if (currentNode.GetEdge(2)==nilNode) {currentNode.GetAuxVar().color='black'; return;} //Trivial case: The added node was at the top 1398 | if (currentNode.GetEdge(2).GetAuxVar().color=='black' || currentNode.GetEdge(2).GetEdge(2)==nilNode) {return;} //Nothing to do if the parent is already black or if the parent is the head node 1399 | 1400 | //At this point we are guaranteed to have an uncle node 1401 | while (currentNode.GetEdge(2).GetAuxVar().color=='red') 1402 | { //As long as the parent is a 'red' node 1403 | 1404 | if (currentNode.GetEdge(2).GetEdge(2).GetEdge(0) == currentNode.GetEdge(2)) 1405 | { //Looking down from the grandparent node, the parent is a left child 1406 | 1407 | let uncleNode = currentNode.GetEdge(2).GetEdge(2).GetEdge(1); //Parent't right sibling node is the uncle 1408 | if (uncleNode.GetAuxVar().color=='red') 1409 | { //CASE 1: Uncle node is red 1410 | currentNode.GetEdge(2).GetAuxVar().color = 'black'; //Set the parent's color to black 1411 | uncleNode.GetAuxVar().color = 'black'; //Set the uncle's color to black 1412 | currentNode.GetEdge(2).GetEdge(2).GetAuxVar().color = 'red'; //Set grandparent color to red 1413 | currentNode = currentNode.GetEdge(2).GetEdge(2); //Set current node to grandparent 1414 | } 1415 | else if (currentNode == currentNode.GetEdge(2).GetEdge(1)) 1416 | { //CASE 2 --> currentNode is a right child 1417 | currentNode = currentNode.GetEdge(2); //Set current node to parent 1418 | RotateLeft(currentNode); //Rotate current node 1419 | currentNode.GetEdge(2).GetAuxVar().color = 'black'; //Set the parent's color to black 1420 | currentNode.GetEdge(2).GetEdge(2).GetAuxVar().color = 'red'; //Set grandparent color to red 1421 | RotateRight(currentNode.GetEdge(2).GetEdge(2)); //Rotate grandparent node 1422 | } 1423 | else 1424 | { 1425 | currentNode.GetEdge(2).GetAuxVar().color = 'black'; //Set the parent's color to black 1426 | currentNode.GetEdge(2).GetEdge(2).GetAuxVar().color = 'red'; //Set grandparent color to red 1427 | RotateRight(currentNode.GetEdge(2).GetEdge(2)); //Rotate grandparent node 1428 | } 1429 | } 1430 | else 1431 | { //Looking down from the grandparent node, the parent is a right child 1432 | let uncleNode = currentNode.GetEdge(2).GetEdge(2).GetEdge(0); //Parent't right sibling node is the uncle 1433 | if(uncleNode.GetAuxVar().color=='red') 1434 | { //CASE 1 --> uncleNode is red 1435 | currentNode.GetEdge(2).GetAuxVar().color = 'black'; //Set the parent's color to black 1436 | uncleNode.GetAuxVar().color = 'black'; //Set the uncle's color to black 1437 | currentNode.GetEdge(2).GetEdge(2).GetAuxVar().color = 'red'; //Set grandparent color to red 1438 | currentNode = currentNode.GetEdge(2).GetEdge(2); //Set current node to grandparent 1439 | } 1440 | else if (currentNode == currentNode.GetEdge(2).GetEdge(0)) 1441 | { //CASE 2 --> currentNode is a left child 1442 | currentNode = currentNode.GetEdge(2); //Set current node to parent 1443 | RotateRight(currentNode); //Rotate current node 1444 | currentNode.GetEdge(2).GetAuxVar().color = 'black'; //Set the parent's color to black 1445 | currentNode.GetEdge(2).GetEdge(2).GetAuxVar().color = 'red'; //Set grandparent color to red 1446 | RotateLeft(currentNode.GetEdge(2).GetEdge(2)); //Rotate grandparent node 1447 | } 1448 | else 1449 | { 1450 | currentNode.GetEdge(2).GetAuxVar().color = 'black'; //Set the parent's color to black 1451 | currentNode.GetEdge(2).GetEdge(2).GetAuxVar().color = 'red'; //Set grandparent color to red 1452 | RotateLeft(currentNode.GetEdge(2).GetEdge(2)); //Rotate grandparent node 1453 | } 1454 | } 1455 | } 1456 | headNode.GetAuxVar().color = 'black'; //in case the head changed color on us in the process 1457 | } 1458 | var RotateLeft = function (xNode) 1459 | { //Does a left rotation 1460 | //Note: the x, y terminology corresponds to fig.13.3, page 314 of the CLR book 1461 | 1462 | //Trivial case 1463 | if (xNode==nilNode || xNode.GetEdge(1)==nilNode) {return false;} //there needs to be a right child for the rotation to complete 1464 | 1465 | let yNode = xNode.GetEdge(1); 1466 | yNode.SetEdge(2, xNode.GetEdge(2)); //switch the parents from x to y 1467 | 1468 | //update the parent to this new change of children 1469 | if (xNode.GetEdge(2)!=nilNode && xNode.GetEdge(2).GetEdge(1)==xNode) {xNode.GetEdge(2).SetEdge(1, yNode); } //xNode is a right child 1470 | else if (xNode.GetEdge(2)!=nilNode && xNode.GetEdge(2).GetEdge(0)==xNode) {xNode.GetEdge(2).SetEdge(0, yNode); } //xNode is a left child 1471 | else if (xNode.GetEdge(2)==nilNode) {headNode = yNode;} 1472 | 1473 | xNode.SetEdge(2, yNode); //Set xNode parent to yNode 1474 | xNode.SetEdge(1, yNode.GetEdge(0)); //Set xNode right to yNode left 1475 | yNode.SetEdge(0, xNode); //Set yNode left to xNode 1476 | if (xNode.GetEdge(1)!=nilNode) {xNode.GetEdge(1).SetEdge(2, xNode);} //Set xNode's right parent to xNode 1477 | } 1478 | var RotateRight = function (xNode) 1479 | { 1480 | //Does a right rotation 1481 | //Note: the x, y terminology corresponds to fig.13.3, page 314 of the CLR book 1482 | 1483 | //Trivial case 1484 | if (xNode==nilNode || xNode.GetEdge(0)==nilNode) {return false;} //there needs to be a left child for the rotation to complete 1485 | 1486 | let yNode = xNode.GetEdge(0); 1487 | yNode.SetEdge(2, xNode.GetEdge(2)); //switch the parents from x to y 1488 | 1489 | //update the parent to this new change of children 1490 | if (xNode.GetEdge(2)!=nilNode && xNode.GetEdge(2).GetEdge(1)==xNode) {xNode.GetEdge(2).SetEdge(1, yNode); } //xNode is a right child 1491 | else if (xNode.GetEdge(2)!=nilNode && xNode.GetEdge(2).GetEdge(0)==xNode) {xNode.GetEdge(2).SetEdge(0, yNode); } //xNode is a left child 1492 | else if (xNode.GetEdge(2)==nilNode) {headNode = yNode;} 1493 | 1494 | xNode.SetEdge(2, yNode); //Set xNode parent to yNode 1495 | xNode.SetEdge(0, yNode.GetEdge(1)); //Set xNode left to yNode left 1496 | yNode.SetEdge(1, xNode); //Set yNode right to xNode 1497 | if (xNode.GetEdge(0)!=nilNode) {xNode.GetEdge(0).SetEdge(2, xNode);} //Set xNode's left parent to xNode 1498 | } 1499 | var BypassNode = function (thisNode, successorNode) 1500 | { //Bypass thisNode replacing it with successorNode. 1501 | //This is a helper function for --> DeleteOne 1502 | //Note: assumes successorNode.left==None and successorNode==thisNode.right 1503 | 1504 | //Transfer the left branch of thisNode to the successorNode 1505 | successorNode.SetEdge(0, thisNode.GetEdge(0)); //Set successorNode left to thisNode left 1506 | thisNode.GetEdge(0).SetEdge(2, successorNode); //Set thisNode>left>parent to successorNode 1507 | successorNode.SetEdge(2, thisNode.GetEdge(2)); //Set successorNode>parent to thisNode>parent 1508 | 1509 | //Notify the parent node link to the successor node as its newly replaced child 1510 | if (thisNode==headNode) {headNode = successorNode; return;} 1511 | if (thisNode.GetEdge(2).GetEdge(1)==thisNode) {thisNode.GetEdge(2).SetEdge(1, successorNode);} 1512 | else {thisNode.GetEdge(2).SetEdge(0, successorNode);} 1513 | } 1514 | var TreeMin = function (startingNode) 1515 | { //The minimum leaf from this branch 1516 | //Returns the node object 1517 | 1518 | if (!startingNode && headNode==nilNode) {return;} 1519 | if (!startingNode && headNode!=nilNode) {startingNode = headNode;} 1520 | 1521 | var currentNode = startingNode; 1522 | while (currentNode.GetEdge(0)!=nilNode) {currentNode = currentNode.GetEdge(0);} 1523 | return currentNode; 1524 | } 1525 | var Lookup = function (data, startingNode) 1526 | { //Lookup 'data' into the tree 1527 | //Returns the node that contains 'data' 1528 | 1529 | var currentNode; 1530 | if (!startingNode) {currentNode = headNode;} else {currentNode = startingNode;} 1531 | 1532 | while (currentNode!=nilNode && currentNode.GetData()!=data) 1533 | { //Navigate the tree to the correct spot 1534 | let comparison = Compare(data,currentNode.GetData()); 1535 | if (comparison< 0) {currentNode = currentNode.GetEdge(0); continue;} 1536 | if (comparison>=0) {currentNode = currentNode.GetEdge(1);} 1537 | } 1538 | return currentNode; 1539 | } 1540 | var PrintTree = function () 1541 | { 1542 | //Using two arrays. One holds the nodes of the current row and in the other we add all the children 1543 | var result = ''; 1544 | var levelCount = 1; 1545 | var currLevel = []; if (headNode!=nilNode) {currLevel.push(headNode);} 1546 | 1547 | while (currLevel.length>0) 1548 | { 1549 | let nextLevel = []; //All children nodes will be stored here 1550 | let nextActCount = 0; //Counts non NIL children nodes 1551 | let currTreeRow = ''; 1552 | let currCount = currLevel.length; //The current level 1553 | for (let i=0; i1) {currTreeRow += '('+currNodeCount+')'} 1566 | if (i0)? nextLevel : []; 1569 | result += (nextActCount>0)? ' '+currTreeRow : ''; levelCount++; 1570 | } 1571 | return result; 1572 | } 1573 | var Compare = function (a, b) {return (compFunct)? compFunct(a,b) : (a==b)? 0 : (a>b)? 1 : -1;} 1574 | 1575 | //Public methods 1576 | this.Delete = function (data) 1577 | { 1578 | if (data===void(0)) {return;} //Nothing to do 1579 | 1580 | var foundNode = Lookup(data); if (foundNode==nilNode) {Say('WARNING: (Delete) Could not delete. Item <'+data+'> was not found in the tree',-1); return;} 1581 | return DeleteOne(foundNode); 1582 | } 1583 | this.Insert = function (data, asData) 1584 | { //Handles an array of things as well as single data (objects, or numbers) 1585 | //Note: if data is an array, then 'asData' can override the default behavior and store the whole array as a single data object instead of spreading it across the tree 1586 | 1587 | //Argument gate 1588 | if (data===void(0)) {return;} //Nothing to do 1589 | if (!IsArray(data) || asData==true) {return AddOne(data);} 1590 | 1591 | //Handle the data being an array scenario 1592 | var count = data.length; 1593 | for (let i=0; i The amount of scrolling that has been done of the viewport area (or any other scrollable element) is taken into account when computing the bounding rectangle. 17 | //Note: This means that the rectangle's boundary edges (top, left, bottom, and right) change their values every time the scrolling position changes 18 | var origBoundingBox = CSSobject.getBoundingClientRect(); //the size of an element and its position relative to the viewport (also accounts for scrolling). 19 | var origStyle = getComputedStyle(obj); //this object reference is not static, it will change if any style is set later 20 | var origInlineStyle = {cssText:CSSobject.style.cssText}; //copies any inline styles (ones inside the HTML doc using the style tag) 21 | var origOpacity = Number(origStyle.opacity); 22 | var origBackgroundColor = new TypeColor(origStyle.backgroundColor); 23 | var origTextColor = new TypeColor(origStyle.color); 24 | var origBorderTopColor = new TypeColor(origStyle.getPropertyValue("border-top-color")); 25 | var origBorderLeftColor = new TypeColor(origStyle.getPropertyValue("border-left-color")); 26 | var origBorderRightColor = new TypeColor(origStyle.getPropertyValue("border-right-color")); 27 | var origBorderBottomColor = new TypeColor(origStyle.getPropertyValue("border-bottom-color")); 28 | var origBorderColor = (origBorderTopColor.IsEqualTo(origBorderLeftColor) && origBorderTopColor.IsEqualTo(origBorderRightColor) && origBorderTopColor.IsEqualTo(origBorderBottomColor)) ? origBorderTopColor : null; 29 | var origGrayscale = 0; 30 | var origZindex = (isNaN(origStyle.zIndex))? 0 : Number(origStyle.zIndex); 31 | var origWinScroll = {left:window.pageXOffset,top:window.pageYOffset}; 32 | 33 | //Methods Private -------------- 34 | var Initialize = function () 35 | { 36 | var filter = origStyle.getPropertyValue("filter"); 37 | var webkitFilter = origStyle.getPropertyValue("-webkit-filter"); 38 | if (filter.search("grayscale")>-1) origGrayscale = Number(100*filter.match(/\d+/)); 39 | else if (webkitFilter.search("grayscale")>-1) origGrayscale = Number(100*webkitFilter.match(/\d+/)); 40 | 41 | ReadInlineStyle(); 42 | } 43 | 44 | var ReadInlineStyle = function () 45 | { 46 | if (CSSobject.style.cssText === "") return; 47 | 48 | var stylesArr = CSSobject.style.cssText.split(";"); 49 | var stylesCount = stylesArr.length; 50 | 51 | for (var i=0;i element that contains rows and cells of a gallery 170 | 171 | //Properties -------------- 172 | var galleryCellArr = []; //array of galleryZoomCell objects 173 | 174 | //Methods ----------------- 175 | var Initialize = function () 176 | { 177 | var rowsArr = galleryObj.children; 178 | var rowCount = rowsArr.length; 179 | for (let i=0;i0)? false : true; 222 | var trig = (this.triggerObj.ReactTo(point,reactionBiasFactor)>0)? false : true; //if "point" is undefined triggerObj checks its DOM events and returns (-1 or Infinity) 223 | var count = buttonArr.length; 224 | for (var i=0;i=0 && relPoint.y<=relEndCorner.y) {return (relPoint.x>=relEndCorner.x)? relPoint.x-relEndCorner.x : (relPoint.x<=0)? -relPoint.x : -1;} 268 | if (relPoint.x>=0 && relPoint.x<=relEndCorner.x) {return (relPoint.y>=relEndCorner.y)? relPoint.y-relEndCorner.y : (relPoint.y<=0)? -relPoint.y : -1;} 269 | //Test the outer corner zones 270 | if (relPoint.x<0 && relPoint.y<0) {return relPoint.Length();} 271 | if (relPoint.x>0 && relPoint.y<0) {relPoint.x-=relEndCorner.x; return relPoint.Length();} 272 | if (relPoint.x>0 && relPoint.y>0) {return relPoint.Minus(relEndCorner).Length();} 273 | if (relPoint.x<0 && relPoint.y>0) {relPoint.y-=relEndCorner.y; return relPoint.Length();} 274 | 275 | return -1; 276 | } 277 | } 278 | this.SetDOMtrigger = function (state) 279 | { 280 | if (!(bodyObj instanceof TypeCSSobject)) return; 281 | useDOMtrigger = state || false; 282 | if (state==true) {bodyObj.GetObjectRef().onmouseover = function() {isActive=true;}; bodyObj.GetObjectRef().onmouseout = function() {isActive=false;}; } 283 | else {bodyObj.GetObjectRef().onmouseover = null; bodyObj.GetObjectRef().onmouseout = null;} 284 | } 285 | this.SetBodyObject = function (thisCSSobject) 286 | { 287 | bodyObj=thisCSSobject; 288 | this.SetAsRectangle(bodyObj.GetLeft(),bodyObj.GetTop(),bodyObj.GetRight(),bodyObj.GetBottom()); 289 | } 290 | this.ReactTo = function (point,reactionBiasFactor) 291 | { 292 | if (useDOMtrigger==true && point===void(0)) {return (isActive==true)? -1 : Infinity;} 293 | if (useDOMtrigger==false && point===void(0)) {return NaN;} 294 | if (reactionBiasFactor===void(0)) {reactionBiasFactor=1;} 295 | 296 | var dist = this.GetDistanceTo(point); 297 | if(dist<=0) {isActive=true;} else {isActive=false;} 298 | if (bodyObj instanceof TypeCSSobject) 299 | { 300 | //A CSS object will react to proximity 301 | var bodyObjWidth = bodyObj.GetWidth(); 302 | var bodyObjHeight = bodyObj.GetHeight(); 303 | var hotRad = (bodyObjWidth1 lengthens the reaction time 305 | bodyObj.ScaleGrayscale(ratio); //Carefull: this will scale the existing grayscale value. If the grayscale is 0% nothing happens. 306 | bodyObj.TopOffOpacity(ratio); //Carefull: this will top off the opacity. If the original was 100%, nothing happens. 307 | } 308 | return dist; 309 | } 310 | this.toString = function () {return "TypeTriggerArea with bodyObj="+bodyObj}; 311 | this.IsActive = function () {return isActive;} 312 | this.Draw = function () {} 313 | 314 | //Initialization 315 | if (arguments.length==3) {this.SetAsCircle(a,b,c);} 316 | else if (arguments.length==4) {this.SetAsRectangle(a,b,c,d);} 317 | else if (arguments.length<=2 && a instanceof TypeCSSobject) {this.SetBodyObject(a); this.SetDOMtrigger(b); } 318 | else if (arguments.length<=2 && a && a.toString().toLowerCase().indexOf("html")>-1 && a.toString().toLowerCase().indexOf("element")>-1) {this.SetBodyObject(new TypeCSSobject(a)); this.SetDOMtrigger(b);} 319 | 320 | } 321 | //--------------------------------------------------------------------------------------------------------------------------------------------------- 322 | function TypeSpringCSS (thisObj,T,elast,fromP,toP,offset,moveFade) 323 | { 324 | 325 | //Private Properties 326 | var mountP; //Starting position under tension 327 | var restP; //Where it will end up at rest 328 | var travelVec; //From mount to rest positions 329 | var travelDist; //Length of the travel vector 330 | var objOffset; 331 | var slider; 332 | var targetObj; 333 | var hotRadius; 334 | var motionFade; 335 | 336 | //Public properties 337 | this.isReactive = true; 338 | 339 | //Private methods 340 | var Initialize = function (thisObj,T,elast,fromP,toP,offset,moveFade) 341 | { 342 | //Argument gate 343 | if (thisObj==void(0)) {Say('WARNING: (TypeSpringCSS) Did not receive an initial object to act on',-1); return;} 344 | 345 | mountP = new TypeXYZw(fromP); 346 | restP = new TypeXYZw(toP); 347 | travelVec = restP.Minus(mountP); //from mount position to resting position 348 | travelDist = travelVec.Length(); 349 | objOffset = new TypeXYZw(offset); 350 | slider = new TypeSlider(travelDist,T,elast); 351 | targetObj = new TypeCSSobject (thisObj); 352 | hotRadius = (targetObj.GetWidth() <= targetObj.GetHeight())? targetObj.GetHeight()*2.5 : targetObj.GetWidth()*2.5; 353 | 354 | motionFade = (moveFade===undefined)? true : moveFade; 355 | targetObj.SetTop(Number(mountP.y + objOffset.y)); 356 | targetObj.SetLeft(Number(mountP.x + objOffset.x)); 357 | } 358 | 359 | //Public Methods --------------------------------------- 360 | this.SetDeploy = function (toThis) {slider.SetDeploy(toThis);} 361 | this.IsResting = function () {return (slider.GetCondition()==1.0) ? true:false;} 362 | this.IsStowed = function () {return (slider.GetCondition()==0.0) ? true:false;} 363 | this.IsMoving = function () {return (slider.GetCondition()==0.5) ? true:false;} 364 | this.GetDeflRatio = function () {return 1-slider.GetState();} 365 | this.ReactTo = function (point) 366 | { 367 | if (slider.GetCondition()==0 || this.isReactive==false || point===undefined) return; 368 | var currPos = mountP.Plus(travelVec.ResizeTo(slider.GetPosition())); 369 | var distance = currPos.Minus(point).Length(); 370 | var ratio = (distance>hotRadius)? 1 : distance/hotRadius; 371 | 372 | //var targetBackColor = new TypeColor(20,20,20); targetObj.BackgroundColorTo(targetBackColor,(1-ratio)); 373 | //var targetTextColor = new TypeColor(230,150,0); targetObj.TextColorTo(targetTextColor,(1-ratio)); 374 | //var targetBorderColor = new TypeColor(230,150,0); targetObj.BorderColorTo(targetBorderColor,(1-ratio)); 375 | targetObj.TopOffOpacity (ratio); //Splits the difference from original opacity to 100% 376 | targetObj.ScaleGrayscale (ratio); //Removes original grayscale (if any) depending on proximity 377 | } 378 | this.Draw = function (ctx) 379 | { 380 | var posRatio = slider.UpdateState(); 381 | var currPos = mountP.Plus(travelVec.ResizeTo(slider.GetPosition())); 382 | 383 | if (motionFade==true && slider.GetCondition()<1) {targetObj.ScaleOpacity(posRatio);} 384 | targetObj.SetTop(Number(currPos.y + objOffset.y)); 385 | targetObj.SetLeft(Number(currPos.x + objOffset.x)); 386 | } 387 | 388 | //Initialization 389 | Initialize(thisObj,T,elast,fromP,toP,offset,moveFade); 390 | } 391 | //--------------------------------------------------------------------------------------------------------------------------------------------------- 392 | var TypeUserInputSensor = function (domElement, contTrack, senseMouse, senseKeyboard, senseTouch) 393 | { //Senses user keyboard and mouse inputs of a DOM element 394 | //Note: DOM = Document Object Model 395 | 396 | //PRIVATE properties 397 | var targetElement; 398 | 399 | var keyCharBuffer; //History of characters entered (keydown and keyup completed) 400 | var keyBufferExpire; //Used to clear the buffer after an interval of inactivity between keyDown events 401 | var keyBufferTimer; //The number of times the GetKeyState() method has been evoked 402 | 403 | var touchBuffer; //History of touches entered 404 | var touchBufferExpire; //Used to clear the buffer after an interval of inactivity between touch events 405 | var touchBufferTimer; // 406 | 407 | var currentKeyPress; 408 | var currentSpecialKeys; //State of Ctrl, Shift, alt, meta keys 409 | 410 | var domElementBox; //the dimensions and position of the domElement's bounding rectangle (this is DOMrect object) 411 | var datumMousePos; //When set, indicates the origin of a mouse drag (moving cursor while mouse is clicked) 412 | var currentMousePos; //In local DOM coordinates 413 | var currentMouseButtons; //Stored as 0/1 bits. 414 | 415 | var currentTouches; //An object holding referenses to touch lists (arrays) 416 | 417 | var isKeyboardTracking; //Boolean. Enable/disable key sensing 418 | var isMouseTracking; //Boolean. Enable/disable mouse sensing 419 | var isContinuousTracking;//Boolean. Track movement continusously (otherwise only track inside the element on mouseDown) 420 | var isMouseOver; //Boolean. Cursor is currently over the element true/false 421 | 422 | var isChanged; 423 | 424 | //PRIVATE methods 425 | var Initialize = function (domElement, contTrack) 426 | { 427 | if (IsString(domElement)) {domElement = document.getElementById(domElement);} 428 | if (!domElement) {Say('WARNING: (TypeInputSensor) Did not receive a proper domElement or domElement ID string',-1); return;} 429 | 430 | targetElement = domElement; 431 | isContinuousTracking = (contTrack)? true : false; 432 | domElementBox = domElement.getBoundingClientRect(); 433 | datumMousePos = new TypeXYZw(); 434 | currentMousePos = new TypeXYZw(); 435 | currentMouseButtons = 0; 436 | 437 | currentKeyPress = 0; 438 | currentSpecialKeys = 0; 439 | keyCharBuffer = ''; //This is a string with all the echoing keypresses 440 | keyBufferExpire = 0; //0:Always expired (no buffer), -1:Infinite buffer 441 | keyBufferTimer = 0; //Increments by the GetKeyState() method 442 | isChanged = {mouse:void(0),keyPress:void(0),keyChar:void(0),touch:void(0)}; 443 | 444 | touchBuffer = []; 445 | touchBufferExpire = 0; //0:Always expired (no buffer), -1:Infinite buffer 446 | touchBufferTimer = 0; 447 | currentTouches = {anywhere:void(0), target:void(0), changed:void(0)}; 448 | 449 | //Note: The 'useCapture' option of the eventListener handles whether to trigger the event during the 'capture' phase or the 'bubble' phase. HTML elements are nested like onions. 450 | //Note: At the "capture" phase events are triggered downward in a parent to child direction. The "bubble" phase triggers upwards from most nested child. 451 | //Note: Adding the same event listener twice has no effect 452 | //arguments: addEventListener(type, listener[, options]); --> using {passive:true} on touch events enhances scrolling performance. (preventDefault() is now disabled) 453 | //arguments: addEventListener(type, listener[, useCapture]); --> useCapture defaults to 'false' 454 | document.addEventListener('keydown', HandleKeyDown); //keydown handles special keys as non silent. keypress is not triggered when shift or alt are pressed alone 455 | document.addEventListener('keyup', HandleKeyUp); 456 | 457 | //Mouse events 458 | domElement.addEventListener('mouseover',HandleMouseOver); 459 | domElement.addEventListener('mouseout',HandleMouseOut); 460 | domElement.addEventListener('mousedown',HandleMouseDown); //mousedown happens inside the element 461 | document.addEventListener ('mouseup',HandleMouseUp); //mouseup is detected at the document lavel 462 | 463 | //Touch events 464 | domElement.addEventListener("touchstart", HandleTouchStart,{passive:true}); //touch point is placed on the touch surface 465 | domElement.addEventListener("touchend", HandleTouchEnd,{passive:true}); //touch point is removed from the touch surface 466 | domElement.addEventListener("touchcancel", HandleTouchCancel,{passive:true}); //touch point has been disrupted (for example, too many touch points are created). 467 | domElement.addEventListener("touchmove", HandleTouchMove,{passive:true}); //touch point is moved along the touch surface 468 | 469 | if (isContinuousTracking) {MouseMovementTracking(true);} 470 | 471 | //...to do... --> handle touch events 472 | } 473 | var SaveSpecialKeysState = function (eventObj) 474 | { 475 | //bit-0: (1) Ctrl key pressed 476 | //bit-1: (2) Shift key pressed 477 | //bit-2: (4) Alt key pressed 478 | //bit-3: (8) Meta key pressed 479 | currentSpecialKeys |= 1*eventObj.ctrlKey; 480 | currentSpecialKeys |= 2*eventObj.shiftKey; 481 | currentSpecialKeys |= 4*eventObj.altKey; 482 | currentSpecialKeys |= 8*eventObj.metaKey; 483 | } 484 | var HandleKeyDown = function (eventObj) 485 | { 486 | eventObj.preventDefault(); //default actions should not be taken as it normally would be (arrow keys no longer scroll, keypress not fired) 487 | currentKeyPress = (eventObj.keycode || eventObj.which); 488 | SaveSpecialKeysState(eventObj); 489 | 490 | //Register the keystroke to the buffers 491 | var keyAlphaNum = (currentKeyPress>= 48 && currentKeyPress<= 90); 492 | var keyNumpad = (currentKeyPress>= 96 && currentKeyPress<=111); 493 | var keySymbols = (currentKeyPress>=186 && currentKeyPress<=222); //brackets, commas, etc 494 | if(keyAlphaNum || keyNumpad || keySymbols) {keyBufferTimer=0; if (IsKeyBufferActive()) {keyCharBuffer += String.fromCharCode(currentKeyPress); isChanged.keyChar=true;}} //The echoing keys 495 | 496 | isChanged.keyPress=true; 497 | } 498 | var HandleKeyUp = function (eventObj) 499 | { 500 | eventObj.preventDefault(); 501 | currentSpecialKeys = 0; 502 | currentKeyPress = 0; 503 | } 504 | var HandleMouseOver = function (eventObj) {isMouseOver=true;} 505 | var HandleMouseOut = function (eventObj) {isMouseOver=false;} 506 | var HandleMouseDown = function (eventObj) 507 | { 508 | if (!isContinuousTracking) {MouseMovementTracking(true);} 509 | eventObj.preventDefault(); 510 | domElementBox = targetElement.getBoundingClientRect(); //Update in case the user scrolled the window 511 | ComputeMousePos(eventObj); //determine the currentMousePos 512 | datumMousePos.SetEqualTo(currentMousePos); //ensuring delta is zero 513 | SaveMouseButtonsState(eventObj); //which moused buttons were pressed during the click 514 | isChanged.mouse=true; 515 | } 516 | var HandleMouseUp = function (eventObj) 517 | { //This event is comming from 'document' 518 | if (!isContinuousTracking) {MouseMovementTracking(false);} 519 | eventObj.preventDefault(); 520 | datumMousePos.SetEqualTo(currentMousePos); //set delta to zero 521 | currentMouseButtons = 0; //clear the saved buttons state 522 | } 523 | var HandleMouseMove = function (eventObj) {eventObj.preventDefault(); ComputeMousePos(eventObj); isChanged.mouse=true;} 524 | var HandleTouchStart = function (eventObj) 525 | { 526 | domElementBox = targetElement.getBoundingClientRect(); //Update in case the user scrolled the window 527 | currentTouches.anywhere = ComputeTouchPosList(eventObj.touches); 528 | currentTouches.target = ComputeTouchPosList(eventObj.targetTouches); 529 | currentTouches.changed = ComputeTouchPosList(eventObj.changedTouches); 530 | SaveSpecialKeysState(eventObj); 531 | //Note: (changedTouches) For the touchstart event this must be a list of the touch points that just became active with the current event. 532 | //Note: (changedTouches) For the touchmove event this must be a list of the touch points that have moved since the last event. 533 | //Note: (changedTouches) For the touchend and touchcancel events this must be a list of the touch points that have just been removed from the surface 534 | 535 | //Register touch to the buffer 536 | var touchCount = currentTouches.target.length; 537 | for (let i=0; i0 && touchBufferTimer0 && keyBufferTimer0 && touchBufferExpire>0 && touchBufferTimer>=touchBufferExpire) {touchBuffer.length=0;} } 599 | var CheckKeyBufferExpire = function () {if(keyCharBuffer!='' && keyBufferExpire>0 && keyBufferTimer>=keyBufferExpire) {keyCharBuffer='';} } 600 | 601 | //PUBLIC 602 | this.ClearKeyBuffer = function () {keyCharBuffer='';} 603 | this.ClearTouchBuffer = function () {touchBuffer.length=0;} 604 | this.SetTouchBufferExpire = function (val) {touchBufferExpire = (isNaN(val))? 0 : val;} 605 | this.SetKeyBufferExpire = function (val) {keyBufferExpire = (isNaN(val))? 0 : val;} 606 | this.SetKeysAsRead = function () {isChanged.keyPress=false; isChanged.keyChar=false;} 607 | this.SetTouchAsRead = function () {isChanged.touch=false;} 608 | this.IsChangedTouch = function () {return isChanged.touch;} 609 | this.IsChangedKeyPress = function () {return isChanged.keyPress;} 610 | this.IsChangedKeyChar = function () {return isChanged.keyChar;} 611 | this.IsChangedMouse = function () {return isChanged.mouse;} 612 | this.IsChangedTouch = function () {return isChanged.touch;} 613 | 614 | this.GetTouchBufferStatus = function () {return IsTouchBufferActive();} 615 | this.GetKeyBufferStatus = function () {return IsKeyBufferActive();} 616 | this.GetMouseState = function () 617 | { 618 | var mouseState = 619 | { 620 | currentPos:currentMousePos, 621 | previousPos:datumMousePos.GetCopy(), 622 | delta:currentMousePos.Minus(datumMousePos), 623 | buttonState:currentMouseButtons, 624 | specialKeys:currentSpecialKeys, 625 | activeArea:domElementBox, 626 | isOver:isMouseOver 627 | }; 628 | 629 | datumMousePos.SetEqualTo(currentMousePos); //Without this there would continue to be a delta even if the mouse stoped moving 630 | return mouseState; 631 | } 632 | this.GetKeyState = function () 633 | { 634 | if (IsKeyBufferActive()) {keyBufferTimer += deltaT;} else {CheckKeyBufferExpire();} 635 | 636 | var keyState = {keyPressed:currentKeyPress, specialKeys:currentSpecialKeys, charBuffer:keyCharBuffer}; 637 | return keyState; 638 | } 639 | this.GetTouchState = function () 640 | { 641 | if (IsTouchBufferActive()) {touchBufferTimer += deltaT;} else {CheckTouchBufferExpire();} 642 | 643 | var touchState = {touchListAny:currentTouches.anywhere, touchListTarget:currentTouches.target, touchListChanged:currentTouches.changed, specialKeys:currentSpecialKeys, targetTouchBuffer:touchBuffer}; 644 | return touchState; 645 | } 646 | 647 | //Initialization 648 | Initialize(domElement, contTrack); 649 | } -------------------------------------------------------------------------------- /EXAMPLE.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasAn73/OpenSource-Javascript-GeometryEngine-and-GraphicsFramework/e0967f377ed38f025455ba0ced1c856efc9f2631/EXAMPLE.zip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Androxman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript Geometry Engine and Graphics framework 2 | A geometry engine and graphics framework that abstracts away from canvas2D and WebGL and is able to handle either one transparent to the programmer 3 | * The following example game implements this framework on Canvas2D -> [Example1](http://www.rayflectar.com/p03-Concepts/p03-MathMoth/game.html) 4 | * The following example game implements this framework on Canvas WebGL -> [Example2](http://www.rayflectar.com/p04-Programming/p01-Rubik/rubik.html) 5 | 6 | The JS files behind both example games are left intentionally readable (via Chrome-->DeveloperTools) 7 | 8 | * A brief explanation outlining the deisgn concept is shown --> [here](https://github.com/ThomasAn73/Javascript-Graphics-Framework/blob/master/CONCEPTUAL%20OUTLINE%20of%20this%20Framework.pdf) 9 | * A preliminary ToDO list can be seen --> [here](https://github.com/ThomasAn73/Javascript-Graphics-Framework/blob/master/TO%20DO%20list) 10 | * An example "HelloWorld" project can be seen --> [here](https://github.com/ThomasAn73/Javascript-Graphics-Framework/blob/master/EXAMPLE.zip) 11 | Extract the ZIP file into a folder, drop the JS files and the two glsl shader files into the same folder and run the Example.html 12 | (you will need to load the html via localhost to avoid XMLHttpRequest denied" on the glsl files. For example if you have python installed on your system open a command prompt and run "python -m http.server") 13 | 14 | ![Screenshot01](http://rayflectar.com/p04-Programming/images/Rcube00-min.jpg) 15 | ![Screenshot02](http://rayflectar.com/p04-Programming/images/Rcube04-min.jpg) 16 | ![Screenshot03](http://rayflectar.com/p04-Programming/images/Rcube03-min.jpg) 17 | -------------------------------------------------------------------------------- /TO DO list: -------------------------------------------------------------------------------- 1 | - Complete the Delauney triangulation object and use it in the TypeSurface object to create meshes from arbitrary planar curve boundaries. 2 | 3 | - Expand the construction history functionality in TypeSurface to include extrusions, volumes of revolution from profile curves, lofts, etc 4 | 5 | - Include more high level "add" methods in the TypeSurface object to create primitive geometric shapes such as spheres, cubes, cones, (and do so parametrically, using curve objects as foundation) 6 | 7 | - Add text functionality to the TypeWebGLpainter object. It should be able to read the TypeText objects and generate either text curves or use textures for each character 8 | 9 | - Implement a grouping mechanism for sceneObjects. Group objects should have their own kinematics. 10 | 11 | - Implement collision detection (eg scene --> method: IsCollided (obj1, obj2)) 12 | 13 | - Improve the shaders to allow for more complex effects (reflections, shadows, etc) and compute more than one lights 14 | 15 | - Move the viewport oparation from the TypeWebGLpainter and into the TypeCameraOperator object. 16 | 17 | - Expand the TypeCameraOperator for more advanced camera movements (eg. following curve paths, like in a roller coaster) 18 | 19 | - Expand on the particles idea inside the TypeCurve object for particle systems (fluid, smoke, clouds, etc) 20 | -------------------------------------------------------------------------------- /WebglFragmentShader.glsl: -------------------------------------------------------------------------------- 1 | 2 | //Thomas Androxman 3 | //--------------------------------------------------------------------------------- 4 | 5 | //Note: fragment shaders don't have a default precision so we need to pick one. 6 | // -> highp float range -> -2^62 to 2^62, int range -> -2^16 to 2^16 7 | // -> mediump float range -> -2^14 to 2^14, int range -> -2^10 to 2^10 8 | // -> lowp float range -> -2^1 to 2^1, int range -> -2^8 to 2^8 9 | precision mediump float; 10 | precision highp int; 11 | precision lowp sampler2D; 12 | 13 | //uniform variables (max is 16)---------------------------------------------------- 14 | uniform vec4 ambientColor; // Scene level. Background illumination 15 | uniform float ambientIntensity; // Scene level. 16 | uniform int appearanceFlags; //Object level. On/Off flags are held as the integer bits 17 | uniform vec4 wireframeColor; //Object level. Only when wireframe falg is on in the appearanceFlags 18 | uniform sampler2D textureUnit[6]; //Object level. Up to 6 texture units per draw call (max is 8) 19 | uniform bool useTextures; //Object level. If no textures, don't bother reading the TextureUnit array 20 | //Note: textureUnit is essentialy an int array. Integers are being passed to it via gl.uniform1iv(shaderVar.textureUnit, [0,1,2,3,4,5]); 21 | //Note: So for textureUnit[0]==0 then -->gl.TEXTURE0<-- will be used 22 | 23 | //non-uniform variables (should not exceed 8)-------------------------------------- 24 | varying vec4 frVertexColor; //Vertex color 25 | varying vec2 frVertexUV; 26 | varying float frTexUnitIdx; //Active texture unit for the current vertex 27 | varying float frVertLightIntensity; //negative value means no response to light 28 | varying vec3 frBaryCoord; //barycentric coordinate from the vertex shader 29 | 30 | //Function declarations------------------------------------------------------------ 31 | vec4 GetValueFromTexture2d (float idx); 32 | bool GetAppearanceStateOnBit (lowp int bitIdx); 33 | vec4 ComputeFaceColor (bool isFullColor); 34 | //--------------------------------------------------------------------------------- 35 | 36 | //Note: Any operations done inside the fragment shader are expensive 37 | void main () 38 | { 39 | //Get appearance bits ------------------------- 40 | bool isFullColor = GetAppearanceStateOnBit(0); 41 | bool showWireframe = GetAppearanceStateOnBit(1); 42 | bool isWireSeeThru = GetAppearanceStateOnBit(2); 43 | //--------------------------------------------- 44 | 45 | //Draw fragment 46 | if ( !showWireframe || (showWireframe && !isWireSeeThru) ) {gl_FragColor = ComputeFaceColor(isFullColor);} //Face color is showing. 47 | else if (showWireframe && isWireSeeThru) {gl_FragColor = vec4(1.0,1.0,1.0,0.8);} 48 | 49 | if (showWireframe && any( lessThan( frBaryCoord, vec3(0.015) ) ) ) {gl_FragColor=wireframeColor;} //The wireframe itself (using standard barycentric) 50 | //if (showWireframe && mod(frBaryCoord.x-floor(frBaryCoord.x),0.2)<0.07) {gl_FragColor=wireframeColor;} 51 | //if (showWireframe && ((frBaryCoord.x*frBaryCoord.y-1.0)*1000.0)<10.0) {gl_FragColor=wireframeColor;} 52 | } 53 | 54 | vec4 ComputeFaceColor (bool isFullColor) 55 | { 56 | vec4 tempColor; 57 | 58 | if (isFullColor && useTextures && frTexUnitIdx>=0.0 && frTexUnitIdx<=5.0) 59 | { 60 | tempColor = GetValueFromTexture2d (frTexUnitIdx); 61 | //tempColor = tempColor*tempColor.w + (1.0-tempColor.w)*frVertexColor; //if both textures and vertex color is used (normal blending) 62 | } 63 | else { tempColor = frVertexColor; } 64 | 65 | if (frVertLightIntensity>=0.0) 66 | { 67 | tempColor = tempColor * vec4((ambientColor.xyz*ambientIntensity + ambientColor.w*frVertLightIntensity)/2.0, 1.0); 68 | } 69 | 70 | return tempColor; 71 | } 72 | 73 | bool GetAppearanceStateOnBit (lowp int bitIdx) 74 | { 75 | float temp = (float(appearanceFlags / int(pow(2.0,float(bitIdx))) )) / 2.0; 76 | return (temp > float(int(temp)) )? true : false; 77 | } 78 | 79 | //Note: This version of glsl does not support dynamic indexing 80 | //Note: The texture units must be indexed manually 81 | vec4 GetValueFromTexture2d (float idx) 82 | { 83 | //Between two vertices the float idx of a texture unit varies from some value to the same value, 84 | //e.g. for the first texture it varies from 0.0 to 0.0 so the only concern is floating point errors and only need a test for idx<0.5 85 | if (idx<0.5) {return texture2D(textureUnit[0], frVertexUV);} 86 | if (idx<1.5) {return texture2D(textureUnit[1], frVertexUV);} 87 | if (idx<2.5) {return texture2D(textureUnit[2], frVertexUV);} 88 | if (idx<3.5) {return texture2D(textureUnit[3], frVertexUV);} 89 | if (idx<4.5) {return texture2D(textureUnit[4], frVertexUV);} 90 | if (idx<5.5) {return texture2D(textureUnit[5], frVertexUV);} 91 | } 92 | -------------------------------------------------------------------------------- /WebglVertexShader.glsl: -------------------------------------------------------------------------------- 1 | 2 | //Thomas Androxman 3 | //--------------------------------------------------------------------------------- 4 | precision mediump float; 5 | precision highp int; 6 | precision lowp sampler2D; 7 | 8 | //uniform variables (max of 128) stay constant for the whole glDraw call 9 | uniform mat4 projViewModelMatrix; //Object level. Already transposed (column major order) 10 | uniform mat4 normalsMatrix; //Object level. The model-matrix inversed and transposed 11 | uniform vec4 defaultColor; //Object level. Default object color (when R = -1 is the signal to use per-vertex color) 12 | uniform vec4 lightColor; // Scene level. Single light source (could be an array in the future) 13 | uniform vec3 lightPosition; // Scene level. Single light position 14 | uniform float lightIntensity; // Scene level. Controls how far the light reaches (throw) 15 | uniform int appearanceFlags; //Object level. On/Off flags are held as the integer bits 16 | //--------------------------------------------------------------------------------- 17 | //attribute variables get fed per vertex from the buffers (should not exceed 8) 18 | attribute vec4 vertexCoord; //the w value contains the triangle index 0,1, or 2 used for barycentric calculation 19 | attribute vec3 vertexNormal; //per vertex normals 20 | attribute vec4 vertexColor; //per vertex color, feeding from attribute=1 of the main code 21 | attribute vec2 vertexUVcoord; //texture coordinates 22 | attribute float vertexTexUnit; //per vertex texture unit index 23 | //--------------------------------------------------------------------------------- 24 | //Output variables to fragment shader 25 | varying vec4 frVertexColor; //Vertex color (will be ignored if there is an opaque texture) 26 | varying vec2 frVertexUV; 27 | varying float frTexUnitIdx; //GL ES1.0 does not support integers as 'varying' 28 | varying float frVertLightIntensity; //Light intensity at the particular vertex 29 | varying vec3 frBaryCoord; //computed barycentric coordinate 30 | //--------------------------------------------------------------------------------- 31 | 32 | //Function declarations------------------------------------------------------------ 33 | vec3 ComputeBarycentric (void); 34 | vec3 ComputeBarycentric2 (); 35 | bool GetAppearanceStateOnBit (lowp int bitIdx); 36 | //--------------------------------------------------------------------------------- 37 | 38 | //Note: appearanceFlags is an integer that stores on/off states in its bits 39 | //bit-0: mask(1) Full color / monochrome 40 | //bit-1: mask(2) Surface wireframe on/off 41 | //bit-2: mask(4) Surface seethru wireframe on/off 42 | //bit-3: mask(8) Surface edges on/off 43 | //bit-4: mask(16) Responds to light 44 | //Note: To set a bit perform a bitwise OR with the mask 45 | //Note: To unset a bit perform a bitwise AND with the NOT mask 46 | //Note: To test for a bit perform a bitwise AND with the mask 47 | 48 | void main () 49 | { 50 | //Get the appearance flags ------------------- 51 | bool respondsToLight = GetAppearanceStateOnBit(4); 52 | bool showWireframe = GetAppearanceStateOnBit(1); 53 | bool isFullColor = GetAppearanceStateOnBit(0); 54 | //-------------------------------------------- 55 | 56 | vec4 monochromeColor = vec4(1.0,1.0,1.0,1.0); 57 | 58 | frVertexUV = vertexUVcoord; //Directly pass to fragment shader 59 | frTexUnitIdx = vertexTexUnit; //Directly pass to fragment shader 60 | 61 | //Output vertex color to fragment shader 62 | if (!isFullColor) {frVertexColor = monochromeColor;} 63 | else if (defaultColor.x<0.0) {frVertexColor = vertexColor;} //If no default color is set, use per vertex colors 64 | else {frVertexColor = defaultColor;} 65 | 66 | //Output vertex light-intensity to fragment shader 67 | if (respondsToLight) 68 | { 69 | vec3 adjustedVertNormal = mat3(normalsMatrix) * vertexNormal; 70 | vec3 adjustedVertCoord = mat3(normalsMatrix) * vec3(vertexCoord); //casting down vertexCoord from vec4 to vec3 71 | vec3 vertToLight = lightPosition - adjustedVertCoord; 72 | 73 | float cosTheta = dot ( normalize(vertToLight), normalize(adjustedVertNormal)); 74 | float LightDistance = length(vertToLight); 75 | 76 | frVertLightIntensity = lightIntensity * (1.0/pow(LightDistance,2.0)) * (1.0+cosTheta); 77 | } 78 | else 79 | { 80 | frVertLightIntensity = -1.0; //Signal the fragment shader not to use light intensity on this vertex 81 | } 82 | 83 | if (showWireframe) {frBaryCoord=ComputeBarycentric();} else {frBaryCoord=vec3(-10.0,-10.0,-10.0);} 84 | 85 | //Output vertex position to the graphics card 86 | gl_Position = projViewModelMatrix * vec4(vertexCoord.x, vertexCoord.y, vertexCoord.z ,1.0); 87 | 88 | } 89 | 90 | bool GetAppearanceStateOnBit (lowp int bitIdx) 91 | { 92 | //There are no bitwise operators in this version of GLSL (need to compute manually) 93 | //Divide by a power of 2 (for the correspnding bit) and test for oddness. Odd = the bit is set 94 | 95 | float temp = (float(appearanceFlags / int(pow(2.0,float(bitIdx))) )) / 2.0; 96 | return (temp > float(int(temp)) )? true : false; 97 | } 98 | 99 | vec3 ComputeBarycentric2 () 100 | { 101 | return vec3(vertexCoord.w+1.0, 1.0/(vertexCoord.w+1.0), 1.0); 102 | } 103 | 104 | vec3 ComputeBarycentric () 105 | { //Assign triangle vertices with predetermined weights (vec3) 106 | //This way during fragment shader interpolation it is possible to tell how close is the fragment to the edge 107 | 108 | //Note: This method fails if vertices are shared, since it is likely that two vertices will have the same barycentric 109 | vec3 result; 110 | float triangleVertIdx = mod( vertexCoord.w , 3.0); //This will result in 0,1, or 2 111 | 112 | if (triangleVertIdx<0.1) {result=vec3(0.0,1.0,0.0);} else if (triangleVertIdx<1.1){result=vec3(1.0,0.0,0.0);} else {result=vec3(0.0,0.0,1.0);} 113 | return result; 114 | } 115 | --------------------------------------------------------------------------------