diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c7b679 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.gradle +**/build +build diff --git a/assets/roads/4way.pixil b/assets/roads/4way.pixil new file mode 100644 index 0000000..6f484fc --- /dev/null +++ b/assets/roads/4way.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":2.1,"website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":64,"height":64,"colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"common":["000000","ffffff","f44336","E91E63","9C27B0","808000","00FF00","008000","00FFFF","008080","0000FF","000080","FF00FF","800080","C0C0C0","808080"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"],"metro ui":["d11141","00b159","00aedb","f37735","ffc425"],"rainbow dash":["ee4035","f37736","fdf498","7bc043","0392cf"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAA80lEQVR4Xu2bQQqDABDE1v8/uj30CREC03hfx51EQdDn2PG5u4edAk3jfHrx+ALQ+nc4vwJsAnZ+BtgE7PwMsAnY+RlgE7DzM8AmYOdngE3Azs+A+71RkYOWSLLptavv8mTx12ZNeq8tQU5UAaS9hdkMWKBIdsgA0t7CbAYsUCQ7ZABpb2E2AxYokh0ygLS3MJsBCxTJDhlA2luYzYAFimSHDCDtLcxmwAJFskMGkPYWZjNggSLZIQP6PoD488Ln6iy+z+X7X4D+sUIfgphAtwBrAAPIAAaAP4Xt/AywCdj5GWATsPMzwCZg52eATcDO/3sDvlsUQj+n5KwGAAAAAElFTkSuQmCC","edit":false,"name":"Background","opacity":1,"active":true,"unqid":"g34noi","options":{"blend":"source-over","locked":false}}],"active":true,"selectedLayer":0,"unqid":"l8lf4n","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAA80lEQVR4Xu2bQQqDABDE1v8/uj30CREC03hfx51EQdDn2PG5u4edAk3jfHrx+ALQ+nc4vwJsAnZ+BtgE7PwMsAnY+RlgE7DzM8AmYOdngE3Azs+A+71RkYOWSLLptavv8mTx12ZNeq8tQU5UAaS9hdkMWKBIdsgA0t7CbAYsUCQ7ZABpb2E2AxYokh0ygLS3MJsBCxTJDhlA2luYzYAFimSHDCDtLcxmwAJFskMGkPYWZjNggSLZIQP6PoD488Ln6iy+z+X7X4D+sUIfgphAtwBrAAPIAAaAP4Xt/AywCdj5GWATsPMzwCZg52eATcDO/3sDvlsUQj+n5KwGAAAAAElFTkSuQmCC","width":64,"height":64}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAA/sfR5H8Fkddasdmnacvx/AABACAYAAACqaXHeAAABKUlEQVR4Xu2bQQ6DMAwEl8/Q/z+n/UwrVJUHZJAWq8OFk1nLOzgkJFuSPclr8f5M8gDxq7q/OKy/5XutFuGd5HjGajyNw/o0eewALB7Wl4C2A219CWg70NaXgLYDbX0JaDvQ1peAtgNtfQloO9DWPwg4ZlTkohMqMiWmuVensnQqfEk87QGXJAFfA0LQ3sT3FsWTgCZ+xbXE87WRAAlgq8K3aGTEREcB+F9AAgh+jgK9P0onufYAe8ANMGz2Ar8Ep3dxmr8E0ApOj5eA6Q7S/CWAVnB6vARMd5DmLwG0gtPjJWC6gzR/CaAVnB7v/gDoIN6t3danPQDv14fLYVifrgpLgCdGPDNU3WhlD6Cn1hwF2uNwW18C2g609SWg7UBbXwLaDrT1/56AD/bVyRx8P9q5AAAAAElFTkSuQmCC","palette_id":false} \ No newline at end of file diff --git a/assets/roads/corner.pixil b/assets/roads/corner.pixil new file mode 100644 index 0000000..eef3ec3 --- /dev/null +++ b/assets/roads/corner.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":2.1,"website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":64,"height":64,"colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"common":["000000","ffffff","f44336","E91E63","9C27B0","808000","00FF00","008000","00FFFF","008080","0000FF","000080","FF00FF","800080","C0C0C0","808080"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"],"metro ui":["d11141","00b159","00aedb","f37735","ffc425"],"rainbow dash":["ee4035","f37736","fdf498","7bc043","0392cf"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABCElEQVR4Xu2WQQrDMBDEnP8/usXQQA+lONmDlPX0ns6sRoEco/Z7jTGO2l+wT1fLbw3g8cdP9yoGBEARIPvyf9LvGtBi/corsDWANsffMaDV8VcBtDv+CoCWx68CaHv8CoDWx/8DMA9fAaT4mKmU+P4QOo/e4vAT2gSwzdq/TLn7KVyxTvVsAKjmAMrEAAC6KjIGqOYAysQAALoqMgao5gDKxAAAuioyBqjmAMrEAAC6KjIGqOYAysQAALoqMgao5gDKxAAAuioyBqjmAMrEAAC6KjIGqOYAysQAALoqMgao5gDKxAAAuioyBqjmAMrEAAC6KjIGqOYAysQAALoqMgao5gDKbG/AG12PEEGwkyZhAAAAAElFTkSuQmCC","edit":false,"name":"Background","opacity":1,"active":true,"unqid":"8clkl8","options":{"blend":"source-over","locked":true}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABCElEQVR4Xu2XQQ7DMBACnf8/upEPkarcIiAx8vTO2rBkVB+j+/cbYxyKBUmsHGzSEgAN4BOAARLHJLEJZMoYIAgEgSAQlDgmiRV6mbRAEAgCQSAocUwSm0CmjAGCQBAIAkGJY5JYoZdJCwSBIBAEghLHJLEJZMoYIAgEgSAQlDgmiRV6mbRAEAgCwX0hKH//k0PNENw6AIv55gZsHYDNfGMDrObbArCbbwogYr4lgJj51QOYxuN3XPGP0CvGr9foKgFcpuMbvz/DZwD/h5ue6Y/HfLaIzw5+HFFIQAChYGvG0oCaVYUuSgNCwdaMpQE1qwpdlAaEgq0ZSwNqVhW6KA0IBVszdvsGnC/qMEFluIx5AAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":1,"active":true,"unqid":"x53vq","options":{"blend":"source-over","locked":false}}],"active":true,"selectedLayer":1,"unqid":"0rxahs","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABTElEQVR4Xu2awQ7EIAhE7f9/9G48NNnDJoY+RKDTa4swwzha22uw6zPGuNgQKBrnp8XjAhD8MXB+QgBODsHPcFyDCABdwOyD3HcoruGpAnBiB/BHp8CrCcgC/ogCMoEPJyAb+FACMoIPIyAr+BACMoPfSsAEPq+n+wSnZX45DG7QL8AbdAXgrjvBKt3+JwdXBSz1lvABEUB9KrvJrUQnBUgBcKnWFFhNsuT35QHyAHkAe1+RCSY3uVV5MkGZoExQJoiMHAWvHCrgvkxQJvhiE8Tyr3T6u+VAtDIBLt0XAdRBA5a5bdK/B662D3CTfkUC3MFX8oAt4KsQsA18dgJCPtpmNMEQ4NlM8Ni/CVMBv8kPLe3sUIMUnXEKEDzmWBFgpqxZgBTQrKFmOFKAmbJmAVJAs4aa4UgBZsqaBUgBzRpqhiMFmClrFvB6BXwBJKFAQV+6n64AAAAASUVORK5CYII=","width":64,"height":64}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAA/sfR5H8Fkddasdmnacvx/AABACAYAAACqaXHeAAABkklEQVR4Xu2bSw7DIAxE3cu09z9Oe5lWVouURRfIM/gjTTZZETPwYg+Q3Mzsbmav4P1pZg+gfTTuagfHv9n3ig7C28z8GdH2aDs4PtJ5ODiBnFIC4OAEcuBJEAHBHACPPAF/T4RwP6IEdMDfEyjcj0gVgIMS3v1VPdIJ6CQ+nYBu4lNzQEfxaQR0FZ9CQGfxRwnw7OpX5UJnZ6FErQJLtAuP+oOdTqMLoGt7mFAXuoRPEX0dZCoBzJnJIoFCALIfUD1oIgBN1JG1QBbeO3FEgAgAS/bE0kf3AaoCpO2pipIoH4DaduWA4K5wBe7/fIF8gHyAfAC2aaO1APGQoiIxygfIB4Abt5NzAIy/v/6TnSBsgnwNNJUAivjJBFDwn0oATfxEAqjipxFAFz+JgCPipxBwTHx3AlKO6Dv6AJ/xtCP6Lk5wiU7/KOP6fcBv4EtuZV+idCFg5yD0yH5DxxyQOhgiYPi5APxaiAARUPfDE4wv41RbVWD4uQBcMkWACMD+HG2RyJBJlA+QD5APgP4eH58DPvMC02rSH7NmAAAAAElFTkSuQmCC","palette_id":false} \ No newline at end of file diff --git a/assets/roads/horizontal.pixil b/assets/roads/horizontal.pixil new file mode 100644 index 0000000..307dd60 --- /dev/null +++ b/assets/roads/horizontal.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":2.1,"website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":64,"height":64,"colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"common":["000000","ffffff","f44336","E91E63","9C27B0","808000","00FF00","008000","00FFFF","008080","0000FF","000080","FF00FF","800080","C0C0C0","808080"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"],"metro ui":["d11141","00b159","00aedb","f37735","ffc425"],"rainbow dash":["ee4035","f37736","fdf498","7bc043","0392cf"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAlUlEQVR4Xu3agQ0AIQzDwLD/0LyeNXxsQLAbIfUsfk78/hMAAuIJUCAOgCFIAQrEE6BAHAAtQIFtt6wBAsqv/98dAQiIJ0CBOACGIAUoEE+AAnEAtAAFKBBPgAJxALQABSgQT4ACcQC0AAUoEE+AAnEAXgvYDyhTYAaUX9+ChA0RKzI+Q1pAC8QToEAcAC1AAQrEE/gAZHwCP2g7a8IAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":1,"active":true,"unqid":"g34noi","options":{"blend":"source-over","locked":false}}],"active":true,"selectedLayer":0,"unqid":"l8lf4n","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAlUlEQVR4Xu3agQ0AIQzDwLD/0LyeNXxsQLAbIfUsfk78/hMAAuIJUCAOgCFIAQrEE6BAHAAtQIFtt6wBAsqv/98dAQiIJ0CBOACGIAUoEE+AAnEAtAAFKBBPgAJxALQABSgQT4ACcQC0AAUoEE+AAnEAXgvYDyhTYAaUX9+ChA0RKzI+Q1pAC8QToEAcAC1AAQrEE/gAZHwCP2g7a8IAAAAASUVORK5CYII=","width":64,"height":64}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAA/sfR5H8Fkddasdmnacvx/AABACAYAAACqaXHeAAAArElEQVR4Xu3byQ3DMAwF0e9m3H89biaJ4HQxTxedJRCcAZdr273tqd7X3pP9hPMB2cefyBcBIkAOkANQgAeEUcgDmGA4/JngdssB2z7/PJC8RAAKoICKkIpQuSiCAiiAAiiAAiigN9gtjfMAHsADeAAP4AE8gAeYEqu2yJkgE2SCTJAJMsG4CZoPqEqQGSEzQvYFTInZGClLEAqgAAqgAAqggL3BblOEB/wKwl8ZuKkci6T+TgAAAABJRU5ErkJggg==","palette_id":false} \ No newline at end of file diff --git a/assets/roads/vertical.pixil b/assets/roads/vertical.pixil new file mode 100644 index 0000000..3e56f71 --- /dev/null +++ b/assets/roads/vertical.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":2.1,"website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":64,"height":64,"colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"common":["000000","ffffff","f44336","E91E63","9C27B0","808000","00FF00","008000","00FFFF","008080","0000FF","000080","FF00FF","800080","C0C0C0","808080"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"],"metro ui":["d11141","00b159","00aedb","f37735","ffc425"],"rainbow dash":["ee4035","f37736","fdf498","7bc043","0392cf"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAw0lEQVR4Xu3SsQHAIAzEQLP/0MkQX1wjeiOD9G4739297Yppeuavy88LTM+/m/l9gDag+RWgDWh+BWgDml8B2oDmV4A2oPkVoA1ofgVoA5pfAdqA5leANqD5FaANaH4FaAOaXwHagOZXgDag+RWgDWh+BWgDml8B2oDmV4A2oPkVoA1ofgVoA5pfAdqA5leANqD5FaANaH4FaAOaXwHagOZXgDag+RWgDWh+BWgDml8B2oDmV4A2oPkVoA1ofgVoA5r/A5fXgEGQGcqgAAAAAElFTkSuQmCC","edit":false,"name":"Background","opacity":1,"active":true,"unqid":"g34noi","options":{"blend":"source-over","locked":false}}],"active":true,"selectedLayer":0,"unqid":"l8lf4n","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAw0lEQVR4Xu3SsQHAIAzEQLP/0MkQX1wjeiOD9G4739297Yppeuavy88LTM+/m/l9gDag+RWgDWh+BWgDml8B2oDmV4A2oPkVoA1ofgVoA5pfAdqA5leANqD5FaANaH4FaAOaXwHagOZXgDag+RWgDWh+BWgDml8B2oDmV4A2oPkVoA1ofgVoA5pfAdqA5leANqD5FaANaH4FaAOaXwHagOZXgDag+RWgDWh+BWgDml8B2oDmV4A2oPkVoA1ofgVoA5r/A5fXgEGQGcqgAAAAAElFTkSuQmCC","width":64,"height":64}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAEAA/sfR5H8Fkddasdmnacvx/AABACAYAAACqaXHeAAAA30lEQVR4Xu3SsRFCMQzAUP9lYP9xYBk4jmOBuHgFSpPKUc7SNTO3mXke3o+ZuS/mT7m/uTX/mu85XcJrZj5vnM5v59b87efXBpbLW/MrQBvQ/ArQBjS/ArQBza8AbUDzK0Ab0PwK0AY0vwK0Ac2vAG1A8ytAG9D8CtAGNL8CtAHNrwBtQPMrQBvQ/ArQBjS/ArQBza8AbUDzK0Ab0PwK0AY0vwK0Ac2vAG1A8ytAG9D8CtAGNL8CtAHNrwBtQPMrQBvQ/ArQBjS/ArQBza8AbUDzK0Ab0PwK0AY0/+8LeAMhz+VqKrIpVQAAAABJRU5ErkJggg==","palette_id":false} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..7f9ffc1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +import org.jetbrains.compose.compose +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.5.10" + id("org.jetbrains.compose") version "0.4.0" +} + +group = "com.bitbit" +version = "1.0" + +repositories { + jcenter() + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") +} + +dependencies { + testImplementation(kotlin("test")) + implementation(project(":math")) + implementation(compose.desktop.currentOs) + implementation(project(":core")) +} + +tasks.test { + useJUnit() +} + +tasks.withType() { + kotlinOptions.jvmTarget = "9" +} + +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "ComposeCity" + packageVersion = "1.0.0" + } + } +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000..49f4722 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,27 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") +} + +group = "com.bitbit" +version = "1.0" + +repositories { + mavenCentral() +} + +tasks.test { + useJUnit() +} + +tasks.withType() { + kotlinOptions.jvmTarget = "9" +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(project(":math")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1") +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/Car.kt b/core/src/main/kotlin/com/birbit/composecity/data/Car.kt new file mode 100644 index 0000000..ff65ff5 --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/Car.kt @@ -0,0 +1,71 @@ +package com.birbit.composecity.data + +import com.curiouscreature.kotlin.math.Float2 +import com.curiouscreature.kotlin.math.length +import com.curiouscreature.kotlin.math.normalize +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlin.time.Duration +import kotlin.time.ExperimentalTime + +typealias Pos = Float2 +fun createPos( + row: Float, + col: Float +) = Pos( + x = col, + y = row +) +val Pos.row:Float + get() = y + +val Pos.col:Float + get() = x + +fun Pos.dist(other: Pos) = length( + other.minus(this) +) + +fun Pos.findPos( + target: Pos, + distance: Double +): Pos { + val vector = target.minus(this) + val normal = normalize(vector) + val result = this + normal.times(distance.toFloat()) + return result +} +class Car( + initialPos: Pos = Pos(0f, 0f) +) { + var speedPerSecond = 30.0 + var targetPath : Path? = null + + @OptIn(ExperimentalTime::class) + internal fun doGameLoop(city: City, delta: Duration) { + val path = this.targetPath ?: return + val radius = speedPerSecond * delta.div(Duration.seconds(1)) + val targetPos = path.target( + pos = pos.value, + radius = radius + ) ?: return + _pos.value = pos.value.findPos( + target = targetPos, + distance = radius + ) + //_orientation.value = (_orientation.value + 1f).mod(360f) + } + + private val _pos = MutableStateFlow(initialPos) + private val _orientation = MutableStateFlow(0f) + + val pos: StateFlow + get() = _pos + + val orientation: StateFlow + get() = _orientation + + companion object { + val CAR_SIZE = 16 + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/City.kt b/core/src/main/kotlin/com/birbit/composecity/data/City.kt new file mode 100644 index 0000000..3eec38f --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/City.kt @@ -0,0 +1,16 @@ +package com.birbit.composecity.data + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class City( + val map: CityMap +) { + private val _cars = MutableStateFlow>(emptyList()) + val cars: StateFlow> + get() = _cars + + fun addCar(car: Car) { + _cars.value = _cars.value + car + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/CityMap.kt b/core/src/main/kotlin/com/birbit/composecity/data/CityMap.kt new file mode 100644 index 0000000..eaac30a --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/CityMap.kt @@ -0,0 +1,29 @@ +package com.birbit.composecity.data + +class CityMap( + val width: Int, + val height: Int +) { + val tiles = buildTiles(width = width, height = height) + + + companion object { + val TILE_SIZE = 64f + fun buildTiles( + width: Int, + height: Int + ): GridImpl { + val result = mutableListOf() + repeat(height) { h -> + repeat(width) { w -> + result.add(Tile(row = h, col = w)) + } + } + return GridImpl( + width = width, + height = height, + data = result + ) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/Entity.kt b/core/src/main/kotlin/com/birbit/composecity/data/Entity.kt new file mode 100644 index 0000000..7859127 --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/Entity.kt @@ -0,0 +1,12 @@ +package com.birbit.composecity.data + +class World( + val tiles: List +) { + +} +interface Entity { + fun doGameLoop( + deltaTime: Float + ) +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/GameLoop.kt b/core/src/main/kotlin/com/birbit/composecity/data/GameLoop.kt new file mode 100644 index 0000000..dbc5ac2 --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/GameLoop.kt @@ -0,0 +1,96 @@ +package com.birbit.composecity.data + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ChannelResult +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.forEach +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import java.util.concurrent.Executors +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.toDuration + +interface Event { + fun apply(city: City) +} + +class ToggleTileEvent( + private val tile: Tile +) : Event { + override fun apply(city: City) { + if (tile.contentValue == TileContent.Grass) { + tile.contentValue = TileContent.Road + } else { + tile.contentValue = TileContent.Grass + } + } +} + +class AddCarEvent( + private val tile: Tile +) : Event { + override fun apply(city: City) { + val newCar = Car( + initialPos = tile.center + ) + // build a path + val path = buildFakePath(city, tile) + newCar.targetPath = path + city.addCar(newCar) + } + + private fun buildFakePath(city: City, tile: Tile): Path? { + val tiles = mutableListOf() + var row = tile.row + 1 + do { + val next = city.map.tiles.maybeGet( + row = row, + col = tile.col) + if (next != null) { + tiles.add(next) + } + row ++ + } while (next != null) + return if (tiles.isEmpty()) { + null + } else { + Path(tiles) + } + } + +} + +@OptIn(ExperimentalTime::class) +class GameLoop( + private val city: City +) { + private val events = Channel( + capacity = Channel.UNLIMITED + ) + private val lastTime: Duration? = null + private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + private val scope = CoroutineScope(Dispatchers.Main + Job()) + + fun start() { + events.consumeAsFlow().onEach { + it.apply(city) + }.launchIn(scope) + scope.launch { + timedLoop( + // well, this should actually sync w/ frame time, but we don't have frame time :) or maybe we do? + period = Duration.milliseconds(16) + ) { delta -> + city.cars.value.forEach { + it.doGameLoop(city, delta) + } + } + } + } + + fun addEvent(event: Event) { + events.trySend(event) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/Grid.kt b/core/src/main/kotlin/com/birbit/composecity/data/Grid.kt new file mode 100644 index 0000000..c960e7b --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/Grid.kt @@ -0,0 +1,46 @@ +package com.birbit.composecity.data + +interface Grid { + val width: Int + val height: Int + val data: List + fun get( + row:Int, + col: Int + ): T { + return data[indexOf(row = row, col = col)] + } + fun maybeGet( + row: Int, + col: Int + ): T? { + return maybeIndexOf( + row = row, + col = col + )?.let { + data[it] + } + } + + fun maybeIndexOf( + row: Int, + col: Int + ): Int? { + if (row < 0 || row >= height) return null + if (col < 0 || col >= width) return null + return row * width + col + + } + fun indexOf( + row: Int, + col: Int + ): Int { + return checkNotNull(maybeIndexOf(row, col)) + } +} + +class GridImpl( + override val width: Int, + override val height: Int, + override val data: List +) : Grid \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/Path.kt b/core/src/main/kotlin/com/birbit/composecity/data/Path.kt new file mode 100644 index 0000000..6e9f55c --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/Path.kt @@ -0,0 +1,23 @@ +package com.birbit.composecity.data + +class Path( + private val tiles : List +) { + private var index = 0 + fun target(pos: Pos, radius: Double): Pos? { + if (index >= tiles.size) return null + val target = tiles[index].center + val distance = target.dist(pos) + if (distance <= radius) { + index ++ + // can make it to it in this frame, pick second one if available + return if (index < tiles.size) { + tiles[index].center + } else { + target + } + } + return target + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/Tile.kt b/core/src/main/kotlin/com/birbit/composecity/data/Tile.kt new file mode 100644 index 0000000..8f020f8 --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/Tile.kt @@ -0,0 +1,40 @@ +package com.birbit.composecity.data + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class Tile( + val row:Int, + val col:Int +) { + private val _content = MutableStateFlow( + TileContent.Grass + ) + val content: StateFlow + get() = _content + + var contentValue: TileContent + get() = _content.value + set(value) { + _content.value = value + } + + val center: Pos = createPos( + row = row * CityMap.TILE_SIZE + CityMap.TILE_SIZE / 2, + col = col * CityMap.TILE_SIZE + CityMap.TILE_SIZE / 2 + ) + + override fun toString() = "Tile[$row/$col]" +} + +sealed class TileContent { + object Grass : TileContent() { + override fun toString() = "Grass" + } + object Road : TileContent() { + override fun toString() = "Road" + } + object OutOfBounds: TileContent() { + override fun toString() = "out of bounds" + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/birbit/composecity/data/TimeSource.kt b/core/src/main/kotlin/com/birbit/composecity/data/TimeSource.kt new file mode 100644 index 0000000..2e8ea8d --- /dev/null +++ b/core/src/main/kotlin/com/birbit/composecity/data/TimeSource.kt @@ -0,0 +1,32 @@ +package com.birbit.composecity.data + +import kotlinx.coroutines.delay +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.toDuration + +@OptIn(ExperimentalTime::class) +private fun now() = System.nanoTime().toDuration(DurationUnit.NANOSECONDS) + +@OptIn(ExperimentalTime::class) +suspend fun timedLoop( + period: Duration, + block : (delta: Duration) -> Unit +) { + var prev = now() + + while(true) { + val current = now() + block(current.minus(prev)) + prev = current + // how much do we wait? + val now = now() + val waitTime = period.minus( + now.minus(current) + ) + if (waitTime > Duration.ZERO) { + delay(waitTime) + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..744e882 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/math/build.gradle.kts b/math/build.gradle.kts new file mode 100644 index 0000000..54cc293 --- /dev/null +++ b/math/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + kotlin("jvm") +} + +group = "com.bitbit" +version = "1.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib")) +} \ No newline at end of file diff --git a/math/src/main/kotlin/com/curiouscreature/kotlin/math/Matrix.kt b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Matrix.kt new file mode 100644 index 0000000..af87fdf --- /dev/null +++ b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Matrix.kt @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2017 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("unused") + +package com.curiouscreature.kotlin.math + +import kotlin.math.* + +enum class MatrixColumn { + X, Y, Z, W +} + +data class Mat2( + var x: Float2 = Float2(x = 1.0f), + var y: Float2 = Float2(y = 1.0f)) { + constructor(m: Mat2) : this(m.x.copy(), m.y.copy()) + + companion object { + fun of(vararg a: Float): Mat2 { + require(a.size >= 4) + return Mat2( + Float2(a[0], a[2]), + Float2(a[1], a[3]) + ) + } + + fun identity() = Mat2() + } + + operator fun get(column: Int) = when(column) { + 0 -> x + 1 -> y + else -> throw IllegalArgumentException("column must be in 0..1") + } + operator fun get(column: Int, row: Int) = get(column)[row] + + operator fun get(column: MatrixColumn) = when(column) { + MatrixColumn.X -> x + MatrixColumn.Y -> y + else -> throw IllegalArgumentException("column must be X or Y") + } + operator fun get(column: MatrixColumn, row: Int) = get(column)[row] + + operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] + operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) + + operator fun set(column: Int, v: Float2) { + this[column].xy = v + } + operator fun set(column: Int, row: Int, v: Float) { + this[column][row] = v + } + + operator fun unaryMinus() = Mat2(-x, -y) + operator fun inc(): Mat2 { + x++ + y++ + return this + } + operator fun dec(): Mat2 { + x-- + y-- + return this + } + + operator fun plus(v: Float) = Mat2(x + v, y + v) + operator fun minus(v: Float) = Mat2(x - v, y - v) + operator fun times(v: Float) = Mat2(x * v, y * v) + operator fun div(v: Float) = Mat2(x / v, y / v) + + operator fun times(m: Mat2) = Mat2( + Float2( + x.x * m.x.x + y.x * m.x.y, + x.y * m.x.x + y.y * m.x.y, + ), + Float2( + x.x * m.y.x + y.x * m.y.y, + x.y * m.y.x + y.y * m.y.y, + ) + ) + + operator fun times(v: Float2) = Float2( + x.x * v.x + y.x * v.y, + x.y * v.x + y.y * v.y, + ) + + + fun toFloatArray() = floatArrayOf( + x.x, y.x, + x.y, y.y + ) + + override fun toString(): String { + return """ + |${x.x} ${y.x}| + |${x.y} ${y.y}| + """.trimIndent() + } + +} + +data class Mat3( + var x: Float3 = Float3(x = 1.0f), + var y: Float3 = Float3(y = 1.0f), + var z: Float3 = Float3(z = 1.0f)) { + constructor(m: Mat3) : this(m.x.copy(), m.y.copy(), m.z.copy()) + + companion object { + fun of(vararg a: Float): Mat3 { + require(a.size >= 9) + return Mat3( + Float3(a[0], a[3], a[6]), + Float3(a[1], a[4], a[7]), + Float3(a[2], a[5], a[8]) + ) + } + + fun identity() = Mat3() + } + + operator fun get(column: Int) = when(column) { + 0 -> x + 1 -> y + 2 -> z + else -> throw IllegalArgumentException("column must be in 0..2") + } + operator fun get(column: Int, row: Int) = get(column)[row] + + operator fun get(column: MatrixColumn) = when(column) { + MatrixColumn.X -> x + MatrixColumn.Y -> y + MatrixColumn.Z -> z + else -> throw IllegalArgumentException("column must be X, Y or Z") + } + operator fun get(column: MatrixColumn, row: Int) = get(column)[row] + + operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] + operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) + + operator fun set(column: Int, v: Float3) { + this[column].xyz = v + } + operator fun set(column: Int, row: Int, v: Float) { + this[column][row] = v + } + + operator fun unaryMinus() = Mat3(-x, -y, -z) + operator fun inc(): Mat3 { + x++ + y++ + z++ + return this + } + operator fun dec(): Mat3 { + x-- + y-- + z-- + return this + } + + operator fun plus(v: Float) = Mat3(x + v, y + v, z + v) + operator fun minus(v: Float) = Mat3(x - v, y - v, z - v) + operator fun times(v: Float) = Mat3(x * v, y * v, z * v) + operator fun div(v: Float) = Mat3(x / v, y / v, z / v) + + operator fun times(m: Mat3) = Mat3( + Float3( + x.x * m.x.x + y.x * m.x.y + z.x * m.x.z, + x.y * m.x.x + y.y * m.x.y + z.y * m.x.z, + x.z * m.x.x + y.z * m.x.y + z.z * m.x.z, + ), + Float3( + x.x * m.y.x + y.x * m.y.y + z.x * m.y.z, + x.y * m.y.x + y.y * m.y.y + z.y * m.y.z, + x.z * m.y.x + y.z * m.y.y + z.z * m.y.z, + ), + Float3( + x.x * m.z.x + y.x * m.z.y + z.x * m.z.z, + x.y * m.z.x + y.y * m.z.y + z.y * m.z.z, + x.z * m.z.x + y.z * m.z.y + z.z * m.z.z, + ) + ) + + operator fun times(v: Float3) = Float3( + x.x * v.x + y.x * v.y + z.x * v.z, + x.y * v.x + y.y * v.y + z.y * v.z, + x.z * v.x + y.z * v.y + z.z * v.z, + ) + + fun toFloatArray() = floatArrayOf( + x.x, y.x, z.x, + x.y, y.y, z.y, + x.z, y.z, z.z + ) + + override fun toString(): String { + return """ + |${x.x} ${y.x} ${z.x}| + |${x.y} ${y.y} ${z.y}| + |${x.z} ${y.z} ${z.z}| + """.trimIndent() + } +} + +data class Mat4( + var x: Float4 = Float4(x = 1.0f), + var y: Float4 = Float4(y = 1.0f), + var z: Float4 = Float4(z = 1.0f), + var w: Float4 = Float4(w = 1.0f)) { + constructor(right: Float3, up: Float3, forward: Float3, position: Float3 = Float3()) : + this(Float4(right), Float4(up), Float4(forward), Float4(position, 1.0f)) + constructor(m: Mat4) : this(m.x.copy(), m.y.copy(), m.z.copy(), m.w.copy()) + + companion object { + fun of(vararg a: Float): Mat4 { + require(a.size >= 16) + return Mat4( + Float4(a[0], a[4], a[8], a[12]), + Float4(a[1], a[5], a[9], a[13]), + Float4(a[2], a[6], a[10], a[14]), + Float4(a[3], a[7], a[11], a[15]) + ) + } + + fun identity() = Mat4() + } + + inline var right: Float3 + get() = x.xyz + set(value) { + x.xyz = value + } + inline var up: Float3 + get() = y.xyz + set(value) { + y.xyz = value + } + inline var forward: Float3 + get() = z.xyz + set(value) { + z.xyz = value + } + inline var position: Float3 + get() = w.xyz + set(value) { + w.xyz = value + } + + inline val scale: Float3 + get() = Float3(length(x.xyz), length(y.xyz), length(z.xyz)) + inline val translation: Float3 + get() = w.xyz + val rotation: Float3 + get() { + val x = normalize(right) + val y = normalize(up) + val z = normalize(forward) + + return when { + z.y <= -1.0f -> Float3(degrees(-HALF_PI), 0.0f, degrees(atan2( x.z, y.z))) + z.y >= 1.0f -> Float3(degrees( HALF_PI), 0.0f, degrees(atan2(-x.z, -y.z))) + else -> Float3( + degrees(-asin(z.y)), degrees(-atan2(z.x, z.z)), degrees(atan2( x.y, y.y))) + } + } + + inline val upperLeft: Mat3 + get() = Mat3(x.xyz, y.xyz, z.xyz) + + operator fun get(column: Int) = when(column) { + 0 -> x + 1 -> y + 2 -> z + 3 -> w + else -> throw IllegalArgumentException("column must be in 0..3") + } + operator fun get(column: Int, row: Int) = get(column)[row] + + operator fun get(column: MatrixColumn) = when(column) { + MatrixColumn.X -> x + MatrixColumn.Y -> y + MatrixColumn.Z -> z + MatrixColumn.W -> w + } + operator fun get(column: MatrixColumn, row: Int) = get(column)[row] + + operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] + operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) + + operator fun set(column: Int, v: Float4) { + this[column].xyzw = v + } + operator fun set(column: Int, row: Int, v: Float) { + this[column][row] = v + } + + operator fun unaryMinus() = Mat4(-x, -y, -z, -w) + operator fun inc(): Mat4 { + x++ + y++ + z++ + w++ + return this + } + operator fun dec(): Mat4 { + x-- + y-- + z-- + w-- + return this + } + + operator fun plus(v: Float) = Mat4(x + v, y + v, z + v, w + v) + operator fun minus(v: Float) = Mat4(x - v, y - v, z - v, w - v) + operator fun times(v: Float) = Mat4(x * v, y * v, z * v, w * v) + operator fun div(v: Float) = Mat4(x / v, y / v, z / v, w / v) + + operator fun times(m: Mat4) = Mat4( + Float4( + x.x * m.x.x + y.x * m.x.y + z.x * m.x.z + w.x * m.x.w, + x.y * m.x.x + y.y * m.x.y + z.y * m.x.z + w.y * m.x.w, + x.z * m.x.x + y.z * m.x.y + z.z * m.x.z + w.z * m.x.w, + x.w * m.x.x + y.w * m.x.y + z.w * m.x.z + w.w * m.x.w, + ), + Float4( + x.x * m.y.x + y.x * m.y.y + z.x * m.y.z + w.x * m.y.w, + x.y * m.y.x + y.y * m.y.y + z.y * m.y.z + w.y * m.y.w, + x.z * m.y.x + y.z * m.y.y + z.z * m.y.z + w.z * m.y.w, + x.w * m.y.x + y.w * m.y.y + z.w * m.y.z + w.w * m.y.w, + ), + Float4( + x.x * m.z.x + y.x * m.z.y + z.x * m.z.z + w.x * m.z.w, + x.y * m.z.x + y.y * m.z.y + z.y * m.z.z + w.y * m.z.w, + x.z * m.z.x + y.z * m.z.y + z.z * m.z.z + w.z * m.z.w, + x.w * m.z.x + y.w * m.z.y + z.w * m.z.z + w.w * m.z.w, + ), + Float4( + x.x * m.w.x + y.x * m.w.y + z.x * m.w.z + w.x * m.w.w, + x.y * m.w.x + y.y * m.w.y + z.y * m.w.z + w.y * m.w.w, + x.z * m.w.x + y.z * m.w.y + z.z * m.w.z + w.z * m.w.w, + x.w * m.w.x + y.w * m.w.y + z.w * m.w.z + w.w * m.w.w, + ) + ) + + operator fun times(v: Float4) = Float4( + x.x * v.x + y.x * v.y + z.x * v.z+ w.x * v.w, + x.y * v.x + y.y * v.y + z.y * v.z+ w.y * v.w, + x.z * v.x + y.z * v.y + z.z * v.z+ w.z * v.w, + x.w * v.x + y.w * v.y + z.w * v.z+ w.w * v.w + ) + + fun toFloatArray() = floatArrayOf( + x.x, y.x, z.x, w.x, + x.y, y.y, z.y, w.y, + x.z, y.z, z.z, w.z, + x.w, y.w, z.w, w.w + ) + + override fun toString(): String { + return """ + |${x.x} ${y.x} ${z.x} ${w.x}| + |${x.y} ${y.y} ${z.y} ${w.y}| + |${x.z} ${y.z} ${z.z} ${w.z}| + |${x.w} ${y.w} ${z.w} ${w.w}| + """.trimIndent() + } +} + +fun transpose(m: Mat2) = Mat2( + Float2(m.x.x, m.y.x), + Float2(m.x.y, m.y.y) +) + +fun transpose(m: Mat3) = Mat3( + Float3(m.x.x, m.y.x, m.z.x), + Float3(m.x.y, m.y.y, m.z.y), + Float3(m.x.z, m.y.z, m.z.z) +) + +@Suppress("LocalVariableName") +fun inverse(m: Mat3): Mat3 { + val a = m.x.x + val b = m.x.y + val c = m.x.z + val d = m.y.x + val e = m.y.y + val f = m.y.z + val g = m.z.x + val h = m.z.y + val i = m.z.z + + val A = e * i - f * h + val B = f * g - d * i + val C = d * h - e * g + + val det = a * A + b * B + c * C + + return Mat3.of( + A / det, B / det, C / det, + (c * h - b * i) / det, (a * i - c * g) / det, (b * g - a * h) / det, + (b * f - c * e) / det, (c * d - a * f) / det, (a * e - b * d) / det + ) +} + +fun transpose(m: Mat4) = Mat4( + Float4(m.x.x, m.y.x, m.z.x, m.w.x), + Float4(m.x.y, m.y.y, m.z.y, m.w.y), + Float4(m.x.z, m.y.z, m.z.z, m.w.z), + Float4(m.x.w, m.y.w, m.z.w, m.w.w) +) + +fun inverse(m: Mat4): Mat4 { + val result = Mat4() + + var pair0 = m.z.z * m.w.w + var pair1 = m.w.z * m.z.w + var pair2 = m.y.z * m.w.w + var pair3 = m.w.z * m.y.w + var pair4 = m.y.z * m.z.w + var pair5 = m.z.z * m.y.w + var pair6 = m.x.z * m.w.w + var pair7 = m.w.z * m.x.w + var pair8 = m.x.z * m.z.w + var pair9 = m.z.z * m.x.w + var pair10 = m.x.z * m.y.w + var pair11 = m.y.z * m.x.w + + result.x.x = pair0 * m.y.y + pair3 * m.z.y + pair4 * m.w.y + result.x.x -= pair1 * m.y.y + pair2 * m.z.y + pair5 * m.w.y + result.x.y = pair1 * m.x.y + pair6 * m.z.y + pair9 * m.w.y + result.x.y -= pair0 * m.x.y + pair7 * m.z.y + pair8 * m.w.y + result.x.z = pair2 * m.x.y + pair7 * m.y.y + pair10 * m.w.y + result.x.z -= pair3 * m.x.y + pair6 * m.y.y + pair11 * m.w.y + result.x.w = pair5 * m.x.y + pair8 * m.y.y + pair11 * m.z.y + result.x.w -= pair4 * m.x.y + pair9 * m.y.y + pair10 * m.z.y + result.y.x = pair1 * m.y.x + pair2 * m.z.x + pair5 * m.w.x + result.y.x -= pair0 * m.y.x + pair3 * m.z.x + pair4 * m.w.x + result.y.y = pair0 * m.x.x + pair7 * m.z.x + pair8 * m.w.x + result.y.y -= pair1 * m.x.x + pair6 * m.z.x + pair9 * m.w.x + result.y.z = pair3 * m.x.x + pair6 * m.y.x + pair11 * m.w.x + result.y.z -= pair2 * m.x.x + pair7 * m.y.x + pair10 * m.w.x + result.y.w = pair4 * m.x.x + pair9 * m.y.x + pair10 * m.z.x + result.y.w -= pair5 * m.x.x + pair8 * m.y.x + pair11 * m.z.x + + pair0 = m.z.x * m.w.y + pair1 = m.w.x * m.z.y + pair2 = m.y.x * m.w.y + pair3 = m.w.x * m.y.y + pair4 = m.y.x * m.z.y + pair5 = m.z.x * m.y.y + pair6 = m.x.x * m.w.y + pair7 = m.w.x * m.x.y + pair8 = m.x.x * m.z.y + pair9 = m.z.x * m.x.y + pair10 = m.x.x * m.y.y + pair11 = m.y.x * m.x.y + + result.z.x = pair0 * m.y.w + pair3 * m.z.w + pair4 * m.w.w + result.z.x -= pair1 * m.y.w + pair2 * m.z.w + pair5 * m.w.w + result.z.y = pair1 * m.x.w + pair6 * m.z.w + pair9 * m.w.w + result.z.y -= pair0 * m.x.w + pair7 * m.z.w + pair8 * m.w.w + result.z.z = pair2 * m.x.w + pair7 * m.y.w + pair10 * m.w.w + result.z.z -= pair3 * m.x.w + pair6 * m.y.w + pair11 * m.w.w + result.z.w = pair5 * m.x.w + pair8 * m.y.w + pair11 * m.z.w + result.z.w -= pair4 * m.x.w + pair9 * m.y.w + pair10 * m.z.w + result.w.x = pair2 * m.z.z + pair5 * m.w.z + pair1 * m.y.z + result.w.x -= pair4 * m.w.z + pair0 * m.y.z + pair3 * m.z.z + result.w.y = pair8 * m.w.z + pair0 * m.x.z + pair7 * m.z.z + result.w.y -= pair6 * m.z.z + pair9 * m.w.z + pair1 * m.x.z + result.w.z = pair6 * m.y.z + pair11 * m.w.z + pair3 * m.x.z + result.w.z -= pair10 * m.w.z + pair2 * m.x.z + pair7 * m.y.z + result.w.w = pair10 * m.z.z + pair4 * m.x.z + pair9 * m.y.z + result.w.w -= pair8 * m.y.z + pair11 * m.z.z + pair5 * m.x.z + + val determinant = m.x.x * result.x.x + m.y.x * result.x.y + m.z.x * result.x.z + m.w.x * result.x.w + + return result / determinant +} + +fun scale(s: Float3) = Mat4(Float4(x = s.x), Float4(y = s.y), Float4(z = s.z)) +fun scale(m: Mat4) = scale(m.scale) + +fun translation(t: Float3) = Mat4(w = Float4(t, 1.0f)) +fun translation(m: Mat4) = translation(m.translation) + +fun rotation(m: Mat4) = Mat4(normalize(m.right), normalize(m.up), normalize(m.forward)) +fun rotation(d: Float3): Mat4 { + val r = transform(d, ::radians) + val c = transform(r) { x -> cos(x) } + val s = transform(r) { x -> sin(x) } + + return Mat4.of( + c.y * c.z, -c.x * s.z + s.x * s.y * c.z, s.x * s.z + c.x * s.y * c.z, 0.0f, + c.y * s.z, c.x * c.z + s.x * s.y * s.z, -s.x * c.z + c.x * s.y * s.z, 0.0f, + -s.y , s.x * c.y , c.x * c.y , 0.0f, + 0.0f , 0.0f , 0.0f , 1.0f + ) +} +fun rotation(axis: Float3, angle: Float): Mat4 { + val x = axis.x + val y = axis.y + val z = axis.z + + val r = radians(angle) + val c = cos(r) + val s = sin(r) + val d = 1.0f - c + + return Mat4.of( + x * x * d + c , x * y * d - z * s, x * z * d + y * s, 0.0f, + y * x * d + z * s, y * y * d + c , y * z * d - x * s, 0.0f, + z * x * d - y * s, z * y * d + x * s, z * z * d + c , 0.0f, + 0.0f , 0.0f , 0.0f , 1.0f + ) +} + +fun normal(m: Mat4) = scale(1.0f / Float3(length2(m.right), length2(m.up), length2(m.forward))) * m + +fun lookAt(eye: Float3, target: Float3, up: Float3 = Float3(z = 1.0f)): Mat4 { + return lookTowards(eye, target - eye, up) +} + +fun lookTowards(eye: Float3, forward: Float3, up: Float3 = Float3(z = 1.0f)): Mat4 { + val f = normalize(forward) + val r = normalize(f x up) + val u = normalize(r x f) + return Mat4(Float4(r), Float4(u), Float4(f), Float4(eye, 1.0f)) +} + +fun perspective(fov: Float, ratio: Float, near: Float, far: Float): Mat4 { + val t = 1.0f / tan(radians(fov) * 0.5f) + val a = (far + near) / (far - near) + val b = (2.0f * far * near) / (far - near) + val c = t / ratio + return Mat4(Float4(x = c), Float4(y = t), Float4(z = a, w = 1.0f), Float4(z = -b)) +} + +fun ortho(l: Float, r: Float, b: Float, t: Float, n: Float, f: Float) = Mat4( + Float4(x = 2.0f / (r - 1.0f)), + Float4(y = 2.0f / (t - b)), + Float4(z = -2.0f / (f - n)), + Float4(-(r + l) / (r - l), -(t + b) / (t - b), -(f + n) / (f - n), 1.0f) +) + diff --git a/math/src/main/kotlin/com/curiouscreature/kotlin/math/Ray.kt b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Ray.kt new file mode 100644 index 0000000..501bff3 --- /dev/null +++ b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Ray.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("unused") + +package com.curiouscreature.kotlin.math + +data class Ray(var origin: Float3 = Float3(), var direction: Float3) + +fun pointAt(r: Ray, t: Float) = r.origin + r.direction * t diff --git a/math/src/main/kotlin/com/curiouscreature/kotlin/math/Scalar.kt b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Scalar.kt new file mode 100644 index 0000000..481c5d0 --- /dev/null +++ b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Scalar.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("NOTHING_TO_INLINE", "unused") + +package com.curiouscreature.kotlin.math + +const val PI = 3.1415926536f +const val HALF_PI = PI * 0.5f +const val TWO_PI = PI * 2.0f +const val FOUR_PI = PI * 4.0f +const val INV_PI = 1.0f / PI +const val INV_TWO_PI = INV_PI * 0.5f +const val INV_FOUR_PI = INV_PI * 0.25f + +inline fun clamp(x: Float, min: Float, max: Float)= if (x < min) min else (if (x > max) max else x) + +inline fun saturate(x: Float) = clamp(x, 0.0f, 1.0f) + +inline fun mix(a: Float, b: Float, x: Float) = a * (1.0f - x) + b * x + +inline fun degrees(v: Float) = v * (180.0f * INV_PI) + +inline fun radians(v: Float) = v * (PI / 180.0f) + +inline fun fract(v: Float) = v % 1 + +inline fun sqr(v: Float) = v * v + +inline fun pow(x: Float, y: Float) = StrictMath.pow(x.toDouble(), y.toDouble()).toFloat() diff --git a/math/src/main/kotlin/com/curiouscreature/kotlin/math/Vector.kt b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Vector.kt new file mode 100644 index 0000000..fd1faae --- /dev/null +++ b/math/src/main/kotlin/com/curiouscreature/kotlin/math/Vector.kt @@ -0,0 +1,1306 @@ +/* + * Copyright (C) 2017 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("NOTHING_TO_INLINE", "unused") + +package com.curiouscreature.kotlin.math + +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt + +enum class VectorComponent { + X, Y, Z, W, + R, G, B, A, + S, T, P, Q +} + +data class Float2(var x: Float = 0.0f, var y: Float = 0.0f) { + constructor(v: Float) : this(v, v) + constructor(v: Float2) : this(v.x, v.y) + + inline var r: Float + get() = x + set(value) { + x = value + } + inline var g: Float + get() = y + set(value) { + y = value + } + + inline var s: Float + get() = x + set(value) { + x = value + } + inline var t: Float + get() = y + set(value) { + y = value + } + + inline var xy: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + + operator fun get(index: VectorComponent) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y + else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") + } + + operator fun get(index1: VectorComponent, index2: VectorComponent): Float2 { + return Float2(get(index1), get(index2)) + } + + operator fun get(index: Int) = when (index) { + 0 -> x + 1 -> y + else -> throw IllegalArgumentException("index must be in 0..1") + } + + operator fun get(index1: Int, index2: Int) = Float2(get(index1), get(index2)) + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Float) = when (index) { + 0 -> x = v + 1 -> y = v + else -> throw IllegalArgumentException("index must be in 0..1") + } + + operator fun set(index1: Int, index2: Int, v: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index: VectorComponent, v: Float) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v + else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + } + + operator fun unaryMinus() = Float2(-x, -y) + operator fun inc(): Float2 { + x += 1.0f + y += 1.0f + return this + } + + operator fun dec(): Float2 { + x -= 1.0f + y -= 1.0f + return this + } + + inline operator fun plus(v: Float) = Float2(x + v, y + v) + inline operator fun minus(v: Float) = Float2(x - v, y - v) + inline operator fun times(v: Float) = Float2(x * v, y * v) + inline operator fun div(v: Float) = Float2(x / v, y / v) + + inline operator fun plus(v: Float2) = Float2(x + v.x, y + v.y) + inline operator fun minus(v: Float2) = Float2(x - v.x, y - v.y) + inline operator fun times(v: Float2) = Float2(x * v.x, y * v.y) + inline operator fun div(v: Float2) = Float2(x / v.x, y / v.y) + + inline fun transform(block: (Float) -> Float): Float2 { + x = block(x) + y = block(y) + return this + } +} + +data class Float3(var x: Float = 0.0f, var y: Float = 0.0f, var z: Float = 0.0f) { + constructor(v: Float) : this(v, v, v) + constructor(v: Float2, z: Float = 0.0f) : this(v.x, v.y, z) + constructor(v: Float3) : this(v.x, v.y, v.z) + + inline var r: Float + get() = x + set(value) { + x = value + } + inline var g: Float + get() = y + set(value) { + y = value + } + inline var b: Float + get() = z + set(value) { + z = value + } + + inline var s: Float + get() = x + set(value) { + x = value + } + inline var t: Float + get() = y + set(value) { + y = value + } + inline var p: Float + get() = z + set(value) { + z = value + } + + inline var xy: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + + operator fun get(index: VectorComponent) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z + else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") + } + + operator fun get(index1: VectorComponent, index2: VectorComponent): Float2 { + return Float2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + + operator fun get(index: Int) = when (index) { + 0 -> x + 1 -> y + 2 -> z + else -> throw IllegalArgumentException("index must be in 0..2") + } + + operator fun get(index1: Int, index2: Int) = Float2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Float) = when (index) { + 0 -> x = v + 1 -> y = v + 2 -> z = v + else -> throw IllegalArgumentException("index must be in 0..2") + } + + operator fun set(index1: Int, index2: Int, v: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index: VectorComponent, v: Float) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v + else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun unaryMinus() = Float3(-x, -y, -z) + operator fun inc(): Float3 { + x += 1.0f + y += 1.0f + z += 1.0f + return this + } + + operator fun dec(): Float3 { + x -= 1.0f + y -= 1.0f + z -= 1.0f + return this + } + + inline operator fun plus(v: Float) = Float3(x + v, y + v, z + v) + inline operator fun minus(v: Float) = Float3(x - v, y - v, z - v) + inline operator fun times(v: Float) = Float3(x * v, y * v, z * v) + inline operator fun div(v: Float) = Float3(x / v, y / v, z / v) + + inline operator fun plus(v: Float2) = Float3(x + v.x, y + v.y, z) + inline operator fun minus(v: Float2) = Float3(x - v.x, y - v.y, z) + inline operator fun times(v: Float2) = Float3(x * v.x, y * v.y, z) + inline operator fun div(v: Float2) = Float3(x / v.x, y / v.y, z) + + inline operator fun plus(v: Float3) = Float3(x + v.x, y + v.y, z + v.z) + inline operator fun minus(v: Float3) = Float3(x - v.x, y - v.y, z - v.z) + inline operator fun times(v: Float3) = Float3(x * v.x, y * v.y, z * v.z) + inline operator fun div(v: Float3) = Float3(x / v.x, y / v.y, z / v.z) + + inline fun transform(block: (Float) -> Float): Float3 { + x = block(x) + y = block(y) + z = block(z) + return this + } +} + +data class Float4( + var x: Float = 0.0f, + var y: Float = 0.0f, + var z: Float = 0.0f, + var w: Float = 0.0f) { + constructor(v: Float) : this(v, v, v, v) + constructor(v: Float2, z: Float = 0.0f, w: Float = 0.0f) : this(v.x, v.y, z, w) + constructor(v: Float3, w: Float = 0.0f) : this(v.x, v.y, v.z, w) + constructor(v: Float4) : this(v.x, v.y, v.z, v.w) + + inline var r: Float + get() = x + set(value) { + x = value + } + inline var g: Float + get() = y + set(value) { + y = value + } + inline var b: Float + get() = z + set(value) { + z = value + } + inline var a: Float + get() = w + set(value) { + w = value + } + + inline var s: Float + get() = x + set(value) { + x = value + } + inline var t: Float + get() = y + set(value) { + y = value + } + inline var p: Float + get() = z + set(value) { + z = value + } + inline var q: Float + get() = w + set(value) { + w = value + } + + inline var xy: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + + inline var rgba: Float4 + get() = Float4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var xyzw: Float4 + get() = Float4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var stpq: Float4 + get() = Float4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + + operator fun get(index: VectorComponent) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z + VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w + } + + operator fun get(index1: VectorComponent, index2: VectorComponent): Float2 { + return Float2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent, + index4: VectorComponent): Float4 { + return Float4(get(index1), get(index2), get(index3), get(index4)) + } + + operator fun get(index: Int) = when (index) { + 0 -> x + 1 -> y + 2 -> z + 3 -> w + else -> throw IllegalArgumentException("index must be in 0..3") + } + + operator fun get(index1: Int, index2: Int) = Float2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Float4 { + return Float4(get(index1), get(index2), get(index3), get(index4)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Float) = when (index) { + 0 -> x = v + 1 -> y = v + 2 -> z = v + 3 -> w = v + else -> throw IllegalArgumentException("index must be in 0..3") + } + + operator fun set(index1: Int, index2: Int, v: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } + + operator fun set(index: VectorComponent, v: Float) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v + VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w = v + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set( + index1: VectorComponent, index2: VectorComponent, + index3: VectorComponent, index4: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } + + operator fun unaryMinus() = Float4(-x, -y, -z, -w) + operator fun inc(): Float4 { + x += 1.0f + y += 1.0f + z += 1.0f + w += 1.0f + return this + } + + operator fun dec(): Float4 { + x -= 1.0f + y -= 1.0f + z -= 1.0f + w -= 1.0f + return this + } + + inline operator fun plus(v: Float) = Float4(x + v, y + v, z + v, w + v) + inline operator fun minus(v: Float) = Float4(x - v, y - v, z - v, w - v) + inline operator fun times(v: Float) = Float4(x * v, y * v, z * v, w * v) + inline operator fun div(v: Float) = Float4(x / v, y / v, z / v, w / v) + + inline operator fun plus(v: Float2) = Float4(x + v.x, y + v.y, z, w) + inline operator fun minus(v: Float2) = Float4(x - v.x, y - v.y, z, w) + inline operator fun times(v: Float2) = Float4(x * v.x, y * v.y, z, w) + inline operator fun div(v: Float2) = Float4(x / v.x, y / v.y, z, w) + + inline operator fun plus(v: Float3) = Float4(x + v.x, y + v.y, z + v.z, w) + inline operator fun minus(v: Float3) = Float4(x - v.x, y - v.y, z - v.z, w) + inline operator fun times(v: Float3) = Float4(x * v.x, y * v.y, z * v.z, w) + inline operator fun div(v: Float3) = Float4(x / v.x, y / v.y, z / v.z, w) + + inline operator fun plus(v: Float4) = Float4(x + v.x, y + v.y, z + v.z, w + v.w) + inline operator fun minus(v: Float4) = Float4(x - v.x, y - v.y, z - v.z, w - v.w) + inline operator fun times(v: Float4) = Float4(x * v.x, y * v.y, z * v.z, w * v.w) + inline operator fun div(v: Float4) = Float4(x / v.x, y / v.y, z / v.z, w / v.w) + + inline fun transform(block: (Float) -> Float): Float4 { + x = block(x) + y = block(y) + z = block(z) + w = block(w) + return this + } +} + +inline operator fun Float.plus(v: Float2) = Float2(this + v.x, this + v.y) +inline operator fun Float.minus(v: Float2) = Float2(this - v.x, this - v.y) +inline operator fun Float.times(v: Float2) = Float2(this * v.x, this * v.y) +inline operator fun Float.div(v: Float2) = Float2(this / v.x, this / v.y) + +inline fun abs(v: Float2) = Float2(abs(v.x), abs(v.y)) +inline fun length(v: Float2) = sqrt(v.x * v.x + v.y * v.y) +inline fun length2(v: Float2) = v.x * v.x + v.y * v.y +inline fun distance(a: Float2, b: Float2) = length(a - b) +inline fun dot(a: Float2, b: Float2) = a.x * b.x + a.y * b.y +fun normalize(v: Float2): Float2 { + val l = 1.0f / length(v) + return Float2(v.x * l, v.y * l) +} + +inline fun reflect(i: Float2, n: Float2) = i - 2.0f * dot(n, i) * n +fun refract(i: Float2, n: Float2, eta: Float): Float2 { + val d = dot(n, i) + val k = 1.0f - eta * eta * (1.0f - sqr(d)) + return if (k < 0.0f) Float2(0.0f) else eta * i - (eta * d + sqrt(k)) * n +} + +inline fun clamp(v: Float2, min: Float, max: Float): Float2 { + return Float2( + clamp(v.x, min, max), + clamp(v.y, min, max) + ) +} + +inline fun clamp(v: Float2, min: Float2, max: Float2): Float2 { + return Float2( + clamp(v.x, min.x, max.x), + clamp(v.y, min.y, max.y) + ) +} + +inline fun mix(a: Float2, b: Float2, x: Float): Float2 { + return Float2( + mix(a.x, b.x, x), + mix(a.y, b.y, x) + ) +} + +inline fun mix(a: Float2, b: Float2, x: Float2): Float2 { + return Float2( + mix(a.x, b.x, x.x), + mix(a.y, b.y, x.y) + ) +} + +inline fun min(v: Float2) = min(v.x, v.y) +inline fun min(a: Float2, b: Float2) = Float2(min(a.x, b.x), min(a.y, b.y)) +inline fun max(v: Float2) = max(v.x, v.y) +inline fun max(a: Float2, b: Float2) = Float2(max(a.x, b.x), max(a.y, b.y)) + +inline fun transform(v: Float2, block: (Float) -> Float) = v.copy().transform(block) + +inline fun lessThan(a: Float2, b: Float) = Bool2(a.x < b, a.y < b) +inline fun lessThan(a: Float2, b: Float2) = Bool2(a.x < b.x, a.y < b.y) +inline fun lessThanEqual(a: Float2, b: Float) = Bool2(a.x <= b, a.y <= b) +inline fun lessThanEqual(a: Float2, b: Float2) = Bool2(a.x <= b.x, a.y <= b.y) +inline fun greaterThan(a: Float2, b: Float) = Bool2(a.x > b, a.y > b) +inline fun greaterThan(a: Float2, b: Float2) = Bool2(a.x > b.y, a.y > b.y) +inline fun greaterThanEqual(a: Float2, b: Float) = Bool2(a.x >= b, a.y >= b) +inline fun greaterThanEqual(a: Float2, b: Float2) = Bool2(a.x >= b.x, a.y >= b.y) +inline fun equal(a: Float2, b: Float) = Bool2(a.x == b, a.y == b) +inline fun equal(a: Float2, b: Float2) = Bool2(a.x == b.x, a.y == b.y) +inline fun notEqual(a: Float2, b: Float) = Bool2(a.x != b, a.y != b) +inline fun notEqual(a: Float2, b: Float2) = Bool2(a.x != b.x, a.y != b.y) + +inline infix fun Float2.lt(b: Float) = Bool2(x < b, y < b) +inline infix fun Float2.lt(b: Float2) = Bool2(x < b.x, y < b.y) +inline infix fun Float2.lte(b: Float) = Bool2(x <= b, y <= b) +inline infix fun Float2.lte(b: Float2) = Bool2(x <= b.x, y <= b.y) +inline infix fun Float2.gt(b: Float) = Bool2(x > b, y > b) +inline infix fun Float2.gt(b: Float2) = Bool2(x > b.x, y > b.y) +inline infix fun Float2.gte(b: Float) = Bool2(x >= b, y >= b) +inline infix fun Float2.gte(b: Float2) = Bool2(x >= b.x, y >= b.y) +inline infix fun Float2.eq(b: Float) = Bool2(x == b, y == b) +inline infix fun Float2.eq(b: Float2) = Bool2(x == b.x, y == b.y) +inline infix fun Float2.neq(b: Float) = Bool2(x != b, y != b) +inline infix fun Float2.neq(b: Float2) = Bool2(x != b.x, y != b.y) + +inline fun any(v: Bool2) = v.x || v.y +inline fun all(v: Bool2) = v.x && v.y + +inline operator fun Float.plus(v: Float3) = Float3(this + v.x, this + v.y, this + v.z) +inline operator fun Float.minus(v: Float3) = Float3(this - v.x, this - v.y, this - v.z) +inline operator fun Float.times(v: Float3) = Float3(this * v.x, this * v.y, this * v.z) +inline operator fun Float.div(v: Float3) = Float3(this / v.x, this / v.y, this / v.z) + +inline fun abs(v: Float3) = Float3(abs(v.x), abs(v.y), abs(v.z)) +inline fun length(v: Float3) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z) +inline fun length2(v: Float3) = v.x * v.x + v.y * v.y + v.z * v.z +inline fun distance(a: Float3, b: Float3) = length(a - b) +inline fun dot(a: Float3, b: Float3) = a.x * b.x + a.y * b.y + a.z * b.z +inline fun cross(a: Float3, b: Float3): Float3 { + return Float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x) +} +inline infix fun Float3.x(v: Float3): Float3 { + return Float3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x) +} +fun normalize(v: Float3): Float3 { + val l = 1.0f / length(v) + return Float3(v.x * l, v.y * l, v.z * l) +} + +inline fun reflect(i: Float3, n: Float3) = i - 2.0f * dot(n, i) * n +fun refract(i: Float3, n: Float3, eta: Float): Float3 { + val d = dot(n, i) + val k = 1.0f - eta * eta * (1.0f - sqr(d)) + return if (k < 0.0f) Float3(0.0f) else eta * i - (eta * d + sqrt(k)) * n +} + +inline fun clamp(v: Float3, min: Float, max: Float): Float3 { + return Float3( + clamp(v.x, min, max), + clamp(v.y, min, max), + clamp(v.z, min, max) + ) +} + +inline fun clamp(v: Float3, min: Float3, max: Float3): Float3 { + return Float3( + clamp(v.x, min.x, max.x), + clamp(v.y, min.y, max.y), + clamp(v.z, min.z, max.z) + ) +} + +inline fun mix(a: Float3, b: Float3, x: Float): Float3 { + return Float3( + mix(a.x, b.x, x), + mix(a.y, b.y, x), + mix(a.z, b.z, x) + ) +} + +inline fun mix(a: Float3, b: Float3, x: Float3): Float3 { + return Float3( + mix(a.x, b.x, x.x), + mix(a.y, b.y, x.y), + mix(a.z, b.z, x.z) + ) +} + +inline fun min(v: Float3) = min(v.x, min(v.y, v.z)) +inline fun min(a: Float3, b: Float3) = Float3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)) +inline fun max(v: Float3) = max(v.x, max(v.y, v.z)) +inline fun max(a: Float3, b: Float3) = Float3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)) + +inline fun transform(v: Float3, block: (Float) -> Float) = v.copy().transform(block) + +inline fun lessThan(a: Float3, b: Float) = Bool3(a.x < b, a.y < b, a.z < b) +inline fun lessThan(a: Float3, b: Float3) = Bool3(a.x < b.x, a.y < b.y, a.z < b.z) +inline fun lessThanEqual(a: Float3, b: Float) = Bool3(a.x <= b, a.y <= b, a.z <= b) +inline fun lessThanEqual(a: Float3, b: Float3) = Bool3(a.x <= b.x, a.y <= b.y, a.z <= b.z) +inline fun greaterThan(a: Float3, b: Float) = Bool3(a.x > b, a.y > b, a.z > b) +inline fun greaterThan(a: Float3, b: Float3) = Bool3(a.x > b.y, a.y > b.y, a.z > b.z) +inline fun greaterThanEqual(a: Float3, b: Float) = Bool3(a.x >= b, a.y >= b, a.z >= b) +inline fun greaterThanEqual(a: Float3, b: Float3) = Bool3(a.x >= b.x, a.y >= b.y, a.z >= b.z) +inline fun equal(a: Float3, b: Float) = Bool3(a.x == b, a.y == b, a.z == b) +inline fun equal(a: Float3, b: Float3) = Bool3(a.x == b.x, a.y == b.y, a.z == b.z) +inline fun notEqual(a: Float3, b: Float) = Bool3(a.x != b, a.y != b, a.z != b) +inline fun notEqual(a: Float3, b: Float3) = Bool3(a.x != b.x, a.y != b.y, a.z != b.z) + +inline infix fun Float3.lt(b: Float) = Bool3(x < b, y < b, z < b) +inline infix fun Float3.lt(b: Float3) = Bool3(x < b.x, y < b.y, z < b.z) +inline infix fun Float3.lte(b: Float) = Bool3(x <= b, y <= b, z <= b) +inline infix fun Float3.lte(b: Float3) = Bool3(x <= b.x, y <= b.y, z <= b.z) +inline infix fun Float3.gt(b: Float) = Bool3(x > b, y > b, z > b) +inline infix fun Float3.gt(b: Float3) = Bool3(x > b.x, y > b.y, z > b.z) +inline infix fun Float3.gte(b: Float) = Bool3(x >= b, y >= b, z >= b) +inline infix fun Float3.gte(b: Float3) = Bool3(x >= b.x, y >= b.y, z >= b.z) +inline infix fun Float3.eq(b: Float) = Bool3(x == b, y == b, z == b) +inline infix fun Float3.eq(b: Float3) = Bool3(x == b.x, y == b.y, z == b.z) +inline infix fun Float3.neq(b: Float) = Bool3(x != b, y != b, z != b) +inline infix fun Float3.neq(b: Float3) = Bool3(x != b.x, y != b.y, z != b.z) + +inline fun any(v: Bool3) = v.x || v.y || v.z +inline fun all(v: Bool3) = v.x && v.y && v.z + +inline operator fun Float.plus(v: Float4) = Float4(this + v.x, this + v.y, this + v.z, this + v.w) +inline operator fun Float.minus(v: Float4) = Float4(this - v.x, this - v.y, this - v.z, this - v.w) +inline operator fun Float.times(v: Float4) = Float4(this * v.x, this * v.y, this * v.z, this * v.w) +inline operator fun Float.div(v: Float4) = Float4(this / v.x, this / v.y, this / v.z, this / v.w) + +inline fun abs(v: Float4) = Float4(abs(v.x), abs(v.y), abs(v.z), abs(v.w)) +inline fun length(v: Float4) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) +inline fun length2(v: Float4) = v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w +inline fun distance(a: Float4, b: Float4) = length(a - b) +inline fun dot(a: Float4, b: Float4) = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w +fun normalize(v: Float4): Float4 { + val l = 1.0f / length(v) + return Float4(v.x * l, v.y * l, v.z * l, v.w * l) +} + +inline fun clamp(v: Float4, min: Float, max: Float): Float4 { + return Float4( + clamp(v.x, min, max), + clamp(v.y, min, max), + clamp(v.z, min, max), + clamp(v.w, min, max) + ) +} + +inline fun clamp(v: Float4, min: Float4, max: Float4): Float4 { + return Float4( + clamp(v.x, min.x, max.x), + clamp(v.y, min.y, max.y), + clamp(v.z, min.z, max.z), + clamp(v.w, min.z, max.w) + ) +} + +inline fun mix(a: Float4, b: Float4, x: Float): Float4 { + return Float4( + mix(a.x, b.x, x), + mix(a.y, b.y, x), + mix(a.z, b.z, x), + mix(a.w, b.w, x) + ) +} + +inline fun mix(a: Float4, b: Float4, x: Float4): Float4 { + return Float4( + mix(a.x, b.x, x.x), + mix(a.y, b.y, x.y), + mix(a.z, b.z, x.z), + mix(a.w, b.w, x.w)) +} + +inline fun min(v: Float4) = min(v.x, min(v.y, min(v.z, v.w))) +inline fun min(a: Float4, b: Float4): Float4 { + return Float4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)) +} +inline fun max(v: Float4) = max(v.x, max(v.y, max(v.z, v.w))) +inline fun max(a: Float4, b: Float4): Float4 { + return Float4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)) +} + +inline fun transform(v: Float4, block: (Float) -> Float) = v.copy().transform(block) + +inline fun lessThan(a: Float4, b: Float) = Bool4(a.x < b, a.y < b, a.z < b, a.w < b) +inline fun lessThan(a: Float4, b: Float4) = Bool4(a.x < b.x, a.y < b.y, a.z < b.z, a.w < b.w) +inline fun lessThanEqual(a: Float4, b: Float) = Bool4(a.x <= b, a.y <= b, a.z <= b, a.w <= b) +inline fun lessThanEqual(a: Float4, b: Float4) = Bool4(a.x <= b.x, a.y <= b.y, a.z <= b.z, a.w <= b.w) +inline fun greaterThan(a: Float4, b: Float) = Bool4(a.x > b, a.y > b, a.z > b, a.w > b) +inline fun greaterThan(a: Float4, b: Float4) = Bool4(a.x > b.y, a.y > b.y, a.z > b.z, a.w > b.w) +inline fun greaterThanEqual(a: Float4, b: Float) = Bool4(a.x >= b, a.y >= b, a.z >= b, a.w >= b) +inline fun greaterThanEqual(a: Float4, b: Float4) = Bool4(a.x >= b.x, a.y >= b.y, a.z >= b.z, a.w >= b.w) +inline fun equal(a: Float4, b: Float) = Bool4(a.x == b, a.y == b, a.z == b, a.w == b) +inline fun equal(a: Float4, b: Float4) = Bool4(a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w) +inline fun notEqual(a: Float4, b: Float) = Bool4(a.x != b, a.y != b, a.z != b, a.w != b) +inline fun notEqual(a: Float4, b: Float4) = Bool4(a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w) + +inline infix fun Float4.lt(b: Float) = Bool4(x < b, y < b, z < b, a < b) +inline infix fun Float4.lt(b: Float4) = Bool4(x < b.x, y < b.y, z < b.z, w < b.w) +inline infix fun Float4.lte(b: Float) = Bool4(x <= b, y <= b, z <= b, w <= b) +inline infix fun Float4.lte(b: Float4) = Bool4(x <= b.x, y <= b.y, z <= b.z, w <= b.w) +inline infix fun Float4.gt(b: Float) = Bool4(x > b, y > b, z > b, w > b) +inline infix fun Float4.gt(b: Float4) = Bool4(x > b.x, y > b.y, z > b.z, w > b.w) +inline infix fun Float4.gte(b: Float) = Bool4(x >= b, y >= b, z >= b, w >= b) +inline infix fun Float4.gte(b: Float4) = Bool4(x >= b.x, y >= b.y, z >= b.z, w >= b.w) +inline infix fun Float4.eq(b: Float) = Bool4(x == b, y == b, z == b, w == b) +inline infix fun Float4.eq(b: Float4) = Bool4(x == b.x, y == b.y, z == b.z, w == b.w) +inline infix fun Float4.neq(b: Float) = Bool4(x != b, y != b, z != b, w != b) +inline infix fun Float4.neq(b: Float4) = Bool4(x != b.x, y != b.y, z != b.z, w != b.w) + +inline fun any(v: Bool4) = v.x || v.y || v.z || v.w +inline fun all(v: Bool4) = v.x && v.y && v.z && v.w + +data class Bool2(var x: Boolean = false, var y: Boolean = false) { + constructor(v: Bool2) : this(v.x, v.y) + + inline var r: Boolean + get() = x + set(value) { + x = value + } + inline var g: Boolean + get() = y + set(value) { + y = value + } + + inline var s: Boolean + get() = x + set(value) { + x = value + } + inline var t: Boolean + get() = y + set(value) { + y = value + } + + inline var xy: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + + operator fun get(index: VectorComponent) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y + else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") + } + + operator fun get(index1: VectorComponent, index2: VectorComponent): Bool2 { + return Bool2(get(index1), get(index2)) + } + + operator fun get(index: Int) = when (index) { + 0 -> x + 1 -> y + else -> throw IllegalArgumentException("index must be in 0..1") + } + + operator fun get(index1: Int, index2: Int) = Bool2(get(index1), get(index2)) + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Boolean) = when (index) { + 0 -> x = v + 1 -> y = v + else -> throw IllegalArgumentException("index must be in 0..1") + } + + operator fun set(index1: Int, index2: Int, v: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index: VectorComponent, v: Boolean) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v + else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + } +} + +data class Bool3(var x: Boolean = false, var y: Boolean = false, var z: Boolean = false) { + constructor(v: Bool2, z: Boolean = false) : this(v.x, v.y, z) + constructor(v: Bool3) : this(v.x, v.y, v.z) + + inline var r: Boolean + get() = x + set(value) { + x = value + } + inline var g: Boolean + get() = y + set(value) { + y = value + } + inline var b: Boolean + get() = z + set(value) { + z = value + } + + inline var s: Boolean + get() = x + set(value) { + x = value + } + inline var t: Boolean + get() = y + set(value) { + y = value + } + inline var p: Boolean + get() = z + set(value) { + z = value + } + + inline var xy: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + + operator fun get(index: VectorComponent) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z + else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") + } + + operator fun get(index1: VectorComponent, index2: VectorComponent): Bool2 { + return Bool2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + + operator fun get(index: Int) = when (index) { + 0 -> x + 1 -> y + 2 -> z + else -> throw IllegalArgumentException("index must be in 0..2") + } + + operator fun get(index1: Int, index2: Int) = Bool2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Boolean) = when (index) { + 0 -> x = v + 1 -> y = v + 2 -> z = v + else -> throw IllegalArgumentException("index must be in 0..2") + } + + operator fun set(index1: Int, index2: Int, v: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index: VectorComponent, v: Boolean) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v + else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } +} + +data class Bool4( + var x: Boolean = false, + var y: Boolean = false, + var z: Boolean = false, + var w: Boolean = false) { + constructor(v: Bool2, z: Boolean = false, w: Boolean = false) : this(v.x, v.y, z, w) + constructor(v: Bool3, w: Boolean = false) : this(v.x, v.y, v.z, w) + constructor(v: Bool4) : this(v.x, v.y, v.z, v.w) + + inline var r: Boolean + get() = x + set(value) { + x = value + } + inline var g: Boolean + get() = y + set(value) { + y = value + } + inline var b: Boolean + get() = z + set(value) { + z = value + } + inline var a: Boolean + get() = w + set(value) { + w = value + } + + inline var s: Boolean + get() = x + set(value) { + x = value + } + inline var t: Boolean + get() = y + set(value) { + y = value + } + inline var p: Boolean + get() = z + set(value) { + z = value + } + inline var q: Boolean + get() = w + set(value) { + w = value + } + + inline var xy: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + + inline var rgba: Bool4 + get() = Bool4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var xyzw: Bool4 + get() = Bool4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var stpq: Bool4 + get() = Bool4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + + operator fun get(index: VectorComponent) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z + VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w + } + + operator fun get(index1: VectorComponent, index2: VectorComponent): Bool2 { + return Bool2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent, + index4: VectorComponent): Bool4 { + return Bool4(get(index1), get(index2), get(index3), get(index4)) + } + + operator fun get(index: Int) = when (index) { + 0 -> x + 1 -> y + 2 -> z + 3 -> w + else -> throw IllegalArgumentException("index must be in 0..3") + } + + operator fun get(index1: Int, index2: Int) = Bool2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Bool4 { + return Bool4(get(index1), get(index2), get(index3), get(index4)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Boolean) = when (index) { + 0 -> x = v + 1 -> y = v + 2 -> z = v + 3 -> w = v + else -> throw IllegalArgumentException("index must be in 0..3") + } + + operator fun set(index1: Int, index2: Int, v: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } + + operator fun set(index: VectorComponent, v: Boolean) = when (index) { + VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v + VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v + VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v + VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w = v + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set( + index1: VectorComponent, index2: VectorComponent, + index3: VectorComponent, index4: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } +} diff --git a/math/src/test/kotlin/com/curiouscreature/kotlin/math/MatrixTest.kt b/math/src/test/kotlin/com/curiouscreature/kotlin/math/MatrixTest.kt new file mode 100644 index 0000000..6acf24e --- /dev/null +++ b/math/src/test/kotlin/com/curiouscreature/kotlin/math/MatrixTest.kt @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2017 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.curiouscreature.kotlin.math + +import org.junit.Assert +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class MatrixTest { + @Test + fun `Mat2 multiplication`() { + val a = Mat2.of(1.0f, 2.0f, 3.0f, 4.0f) + val b = Mat2.of(2.0f, 0.0f, 1.0f, 2.0f) + assertEquals( + Mat2.of(4.0f, 4.0f, 10.0f, 8.0f), + a * b + ) + } + + @Test + fun `Mat3 multiplication`() { + val a = Mat3.of(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f) + val b = Mat3.of(2.0f, 0.0f, 1.0f, 2.0f, 3.0f, 1.0f, 2.0f, 0.0f, 1.0f) + assertEquals( + Mat3.of(12.0f, 6.0f, 6.0f, 30.0f, 15.0f, 15.0f, 48.0f, 24.0f, 24.0f), + a * b + ) + } + + @Test + fun `Mat4 multiplication`() { + val a = Mat4.of( + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f, + ) + val b = Mat4.of( + 2.0f, 0.0f, 1.0f, 2.0f, + 1.0f, 1.0f, 2.0f, 0.0f, + 2.0f, 1.0f, 2.0f, 2.0f, + 0.0f, 1.0f, 1.0f, 2.0f, + ) + assertEquals( + Mat4.of( + 10.0f, 9.0f, 15.0f, 16.0f, + 30.0f, 21.0f, 39.0f, 40.0f, + 50.0f, 33.0f, 63.0f, 64.0f, + 70.0f, 45.0f, 87.0f, 88.0f + ), + a * b + ) + } + + @Test + fun `Mat3 identity`() { + assertEquals( + Mat3( + Float3(1f, 0f, 0f), + Float3(0f, 1f, 0f), + Float3(0f, 0f, 1f) + ), + Mat3.identity() + ) + } + + @Test(expected = IllegalArgumentException::class) + fun `Mat3 of fails if less than 9 arguments`() { + Mat3.of(*8.floatArray()) + } + + @Test + fun `Mat3 of`() { + assertEquals(MAT_3, Mat3.of(*9.floatArray())) + } + + @Test + fun `Mat4 identity`() { + assertEquals( + Mat4( + Float4(1f, 0f, 0f, 0f), + Float4(0f, 1f, 0f, 0f), + Float4(0f, 0f, 1f, 0f), + Float4(0f, 0f, 0f, 1f) + ), + Mat4.identity() + ) + } + + @Test(expected = IllegalArgumentException::class) + fun `Mat4 of fails if less than 16 arguments`() { + Mat4.of(*15.floatArray()) + } + + @Test + fun `Mat4 of`() { + assertEquals(MAT_4, Mat4.of(*16.floatArray())) + } + + @Test + fun `transpose Mat3`() { + assertEquals( + Mat3( + Float3(1f, 2f, 3f), + Float3(4f, 5f, 6f), + Float3(7f, 8f, 9f) + ), + transpose(MAT_3) + ) + } + + @Test + fun `transpose Mat3 of identity is identity`() { + assertEquals(transpose(Mat3.identity()), Mat3.identity()) + } + + @Test + fun `inverse Mat3`() { + assertEquals( + Mat3( + Float3(0f, 1f, 0f), + Float3(-2f, 1f, 1f), + Float3(2f, -2f, 0f) + ), + inverse(Mat3( + Float3(1f, 0f, 0.5f), + Float3(1f, 0f, 0f), + Float3(1f, 1f, 1f) + )) + ) + } + + @Test + fun `inverse Mat3 of identity is identity`() { + assertEquals(Mat3.identity(), inverse(Mat3.identity())) + } + + @Test + fun `scale Float3`() { + assertEquals(Mat4.identity(), scale(Float3(1f, 1f, 1f))) + } + + @Test + fun `scale Mat4`() { + assertEquals( + Mat4( + Float4(2f, 0f, 0f, 0f), + Float4(0f, 4f, 0f, 0f), + Float4(0f, 0f, 6f, 0f), + Float4(0f, 0f, 0f, 1f) + ), + scale(Mat4( + Float4(2f, 0f, 0f, 0f), + Float4(4f, 0f, 0f, 0f), + Float4(6f, 0f, 0f, 0f), + Float4(0f, 0f, 0f, 0f) + )) + ) + } + + @Test + fun `translation Float3`() { + assertEquals( + Mat4( + Float4(1f, 0f, 0f, 0f), + Float4(0f, 1f, 0f, 0f), + Float4(0f, 0f, 1f, 0f), + Float4(1f, 2f, 3f, 1f) + ), + translation(Float3(1f, 2f, 3f)) + ) + } + + @Test + fun `translation Mat4`() { + assertEquals( + Mat4( + Float4(1f, 0f, 0f, 0f), + Float4(0f, 1f, 0f, 0f), + Float4(0f, 0f, 1f, 0f), + Float4(4f, 8f, 12f, 1f) + ), + translation(MAT_4) + ) + } + + @Test + fun `inverse Mat4 of identity is identity`() { + assertEquals(Mat4.identity(), inverse(Mat4.identity())) + } + + @Test + fun `inverse Mat4`() { + assertEquals( + Mat4( + Float4( 1f, 0f, 0f, 0f), + Float4(-1f, 1f, 0f, 0f), + Float4( 4f, -4f, 1f, -2f), + Float4(-2f, 2f, 0f, 1f) + + ), + inverse( + Mat4( + Float4(1f, 0f, 0f, 0f), + Float4(1f, 1f, 0f, 0f), + Float4(0f, 0f, 1f, 2f), + Float4(0f, -2f, 0f, 1f) + )) + ) + } + + @Test + fun `inverse non-invertible Mat4`() { + assertEquals( + Mat4( + Float4(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN, Float.NaN), + Float4(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NaN, Float.NaN), + Float4(Float.NaN, Float.NaN, Float.NaN, Float.NaN), + Float4(Float.NaN, Float.NaN, Float.NaN, Float.NaN) + + ), + inverse( + Mat4( + Float4(1f, 1f, 0f, 0f), + Float4(1f, 1f, 0f, 0f), + Float4(0f, 0f, 1f, 2f), + Float4(0f, 0f, 0f, 1f) + )) + ) + } + + @Test + fun `rotation Float3`() { + assertArrayEquals( + Mat4( + Float4(0.998f, 0.0523f, -0.0348f, 0f), + Float4(-0.0517f, 0.9985f, 0.0174f, 0f), + Float4(0.0357f, -0.0156f, 0.9992f, 0f), + Float4(0f, 0f, 0f, 1f) + ).toFloatArray(), + rotation(Float3(1f, 2f, 3f)).toFloatArray() + ) + } + + @Test + fun `rotation Mat4`() { + assertArrayEquals( + Mat4( + Float4(0.0966f, 0.4833f, 0.87f, 0f), + Float4(0.169f, 0.507f, 0.8451f, 0f), + Float4(0.2242f, 0.5232f, 0.8221f, 0f), + Float4(0f, 0f, 0f, 1f) + ).toFloatArray(), + rotation(MAT_4).toFloatArray() + ) + } + + @Test + fun `rotation axis angle`() { + assertArrayEquals( + Mat4( + Float4(0.9999f, 5f, 1f, 0f), + Float4(-1f, 4f, 7f, 0f), + Float4(5f, 5f, 9f, 0f), + Float4(0f, 0f, 0f, 1f) + ).toFloatArray(), + rotation(Float3(1f, 2f, 3f), 90f).toFloatArray() + ) + } + + @Test + fun normal() { + assertArrayEquals( + Mat4( + Float4(0.0093f, 0.0357f, 0.0502f, 13.0f), + Float4(0.0186f, 0.0428f, 0.0558f, 14.0f), + Float4(0.0280f, 0.05f, 0.0614f, 15.0f), + Float4(0.0373f, 0.0571f, 0.0670f, 16.0f) + ).toFloatArray(), + normal(MAT_4).toFloatArray() + ) + } + + @Test + fun lookAt() { + assertArrayEquals( + Mat4( + Float4(0.53606f, -0.7862f, 0.30734f, 0.0f), + Float4(0.28377f, 0.51073f, 0.81155f, 0.0f), + Float4(0.79504f, 0.34783f, -0.4969f, 0.0f), + Float4(1.0f, 2.0f, 3.0f, 1.0f) + ).toFloatArray(), + lookAt( + eye = Float3(1f, 2f, 3f), + target = Float3(9f, 5.5f, -2f), + up = Float3(3f, 4f, 5f) + ).toFloatArray() + ) + } + + @Test + fun lookTowards() { + assertArrayEquals( + Mat4( + Float4(-0.6549f, -0.3475f, 0.67100f, 0.0f), + Float4(0.10792f, 0.83584f, 0.53825f, 0.0f), + Float4(0.74791f, -0.4249f, 0.50994f, 0.0f), + Float4(1.0f, 2.0f, 3.0f, 1.0f) + ).toFloatArray(), + lookTowards( + eye = Float3(1f, 2f, 3f), + forward = Float3(4.4f, -2.5f, 3f), + up = Float3(3f, 4f, 5f) + ).toFloatArray() + ) + } + + @Test + fun perspective() { + assertArrayEquals( + Mat4( + Float4(57.2943f, 0.0f, 0.0f, 0.0f), + Float4(0.0f, 114.5886f, 0.0f, 0.0f), + Float4(0.0f, 0.0f, -7.0f, 1.0f), + Float4(0.0f, 0.0f, 24.0f, 0.0f) + ).toFloatArray(), + perspective( + fov = 1f, + ratio = 2f, + far = 3f, + near = 4f + ).toFloatArray() + ) + } + + @Test + fun ortho() { + assertArrayEquals( + Mat4( + Float4(2.0f, 0.0f, 0.0f, 0.0f), + Float4(0.0f, 2.0f, 0.0f, 0.0f), + Float4(0.0f, 0.0f, -2.0f, 0.0f), + Float4(-3.0f, -7.0f, -11.0f, 1.0f) + ).toFloatArray(), + ortho( + l = 1f, + r = 2f, + b = 3f, + t = 4f, + n = 5f, + f = 6f + ).toFloatArray() + ) + } + + companion object { + private val MAT_3 = Mat3( + Float3(1f, 4f, 7f), + Float3(2f, 5f, 8f), + Float3(3f, 6f, 9f) + ) + private val MAT_4 = Mat4( + Float4(1f, 5f, 9f, 13f), + Float4(2f, 6f, 10f, 14f), + Float4(3f, 7f, 11f, 15f), + Float4(4f, 8f, 12f, 16f) + ) + + private fun assertArrayEquals(expected: FloatArray, actual: FloatArray, delta: Float = 0.0001f) = + Assert.assertArrayEquals(expected, actual, delta) + + /** + * @return a FloatArray containing n floats 1f,2f,...,n (float) where n + * is the @receiver integer. + */ + private fun Int.floatArray() = FloatArray(this) { (it + 1).toFloat() } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..6c06963 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } + +} +rootProject.name = "ComposeCity" +include("core") +include("math") diff --git a/src/main/kotlin/ImageCache.kt b/src/main/kotlin/ImageCache.kt new file mode 100644 index 0000000..218a1ab --- /dev/null +++ b/src/main/kotlin/ImageCache.kt @@ -0,0 +1,11 @@ +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.imageFromResource + +object ImageCache { + private val resourceCache = mutableMapOf() + fun loadResource(name:String) : ImageBitmap { + return resourceCache.getOrPut(name) { + imageFromResource(name) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..2be2655 --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,246 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +import androidx.compose.desktop.Window +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.unit.dp +import com.birbit.composecity.data.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.forEach + +val SCALE = 1f +val TILE_SIZE_DP = (CityMap.TILE_SIZE * SCALE).dp +val CAR_SIZE_DP = (Car.CAR_SIZE * SCALE).dp + +fun main() = Window { + val uiControls = UIControls() + val city = City( + map = CityMap(width = 20, height = 20) + ) + val gameLoop = GameLoop(city) + gameLoop.start() + + val uiCallbacks = object : ControlCallbacks { + override fun onCarMenuClick() { + if (uiControls.modeValue == Mode.ADD_CAR) { + uiControls.setMode(Mode.CHANGE_TILE) + } else { + uiControls.setMode(Mode.ADD_CAR) + } + } + + override fun onTileClick(tile: Tile) { + if (uiControls.modeValue == Mode.CHANGE_TILE) { + gameLoop.addEvent(ToggleTileEvent(tile)) + } else if (uiControls.modeValue == Mode.ADD_CAR && tile.contentValue == TileContent.Road) { + gameLoop.addEvent(AddCarEvent(tile)) + } + } + } + MaterialTheme { + Box { + CityMapUI(city.map, city.cars, uiCallbacks) + ControlsUI( + controls = uiControls, + callbacks = uiCallbacks, + modifier = Modifier.align(Alignment.BottomCenter) + ) + } + + } +} + +interface ControlCallbacks { + fun onCarMenuClick() + fun onTileClick(tile: Tile) +} + +@Composable +fun ControlsUI( + controls: UIControls, + callbacks: ControlCallbacks, + modifier: Modifier = Modifier +) { + val mode by controls.mode.collectAsState() + + Box( + modifier = modifier.fillMaxWidth(.8f).height(TILE_SIZE_DP) + ) { + Row( + modifier = Modifier.wrapContentSize( + align = Alignment.Center + ).background( + Color.LightGray + ) + ) { + Button( + onClick = callbacks::onCarMenuClick + ) { + Image( + bitmap = ImageCache.loadResource("car.png"), + contentDescription = "toggle car", + colorFilter = ColorFilter.tint( + color = if (mode == Mode.ADD_CAR) { + Color.Black + } else { + Color.Gray + } + ) + ) + } + + } + } +} + +@Composable +fun CityMapUI( + cityMap: CityMap, + cars: StateFlow>, + controlCallbacks: ControlCallbacks +) { + val currentCars by cars.collectAsState() + Box { + Column { + repeat(cityMap.height) { row -> + Row { + repeat(cityMap.width) { col -> + TileUI( + cityMap, + cityMap.tiles.get(row = row, col = col), + controlCallbacks::onTileClick + ) + } + } + } + } + currentCars.forEach { + CarUI(cityMap, it) + } + } +} + +@Composable +fun CarUI( + cityMap: CityMap, + car: Car +) { + val pos by car.pos.collectAsState() + val rotation by car.orientation.collectAsState() + Image( + bitmap = ImageCache.loadResource("car.png"), + contentDescription = "car", + modifier = Modifier.absoluteOffset( + x = (pos.col * SCALE).dp - CAR_SIZE_DP, + y = (pos.row * SCALE).dp - CAR_SIZE_DP + ).rotate( + rotation + ) + ) +} + +@Composable +fun TileUI( + cityMap: CityMap, + tile: Tile, + onClickHandler: (Tile) -> Unit +) { + val content by tile.content.collectAsState() + Box( + modifier = Modifier.size(TILE_SIZE_DP).clickable { + onClickHandler(tile) + } + ) { + when (content) { + TileContent.Grass -> Box( + modifier = Modifier.background(Color.Green).fillMaxSize() + ) + is TileContent.Road -> Image( + bitmap = getTileBitmap( + cityMap = cityMap.tiles, + tile = tile + ), + contentDescription = null, + ) + is TileContent.OutOfBounds -> { + // nada + } + } + + } +} + +private val baseState = MutableStateFlow(TileContent.OutOfBounds) +private fun TileContent.roadMask(shift: Int) = if (isRoad()) { + 1.shl(shift) +} else { + 0 +} + +private fun TileContent.isRoad() = this == TileContent.Road + +@Composable +private fun getTileBitmap( + cityMap: Grid, + tile: Tile +): ImageBitmap { + check(tile.contentValue == TileContent.Road) + val north by (cityMap.maybeGet( + tile.row - 1, + tile.col + )?.content ?: baseState).collectAsState() + val south by (cityMap.maybeGet( + tile.row + 1, + tile.col + )?.content ?: baseState).collectAsState() + val west by (cityMap.maybeGet( + tile.row, + tile.col - 1 + )?.content ?: baseState).collectAsState() + val east by (cityMap.maybeGet( + tile.row, + tile.col + 1 + )?.content ?: baseState).collectAsState() + val mask = north.roadMask(0) + .or( + west.roadMask(1) + ) + .or( + south.roadMask(2) + ).or( + east.roadMask(3) + ) + return ImageCache.loadResource( + roadAssetMapping[mask] ?: "sample.png" + ) +} + +private val roadAssetMapping = mutableMapOf( + 0b1111 to "4way.png", + 0b1110 to "all-but-north.png", + 0b1101 to "all-but-west.png", + 0b1011 to "all-but-south.png", + 0b0111 to "all-but-east.png", + 0b0011 to "north-west.png", + 0b1001 to "north-east.png", + 0b0110 to "south-west.png", + 0b1100 to "south-east.png", + 0b0101 to "vertical.png", + 0b0100 to "vertical.png", + 0b0001 to "vertical.png", + 0b1010 to "horizontal.png", + 0b0010 to "horizontal.png", + 0b1000 to "horizontal.png", +) diff --git a/src/main/kotlin/UIControls.kt b/src/main/kotlin/UIControls.kt new file mode 100644 index 0000000..c753d73 --- /dev/null +++ b/src/main/kotlin/UIControls.kt @@ -0,0 +1,18 @@ +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class UIControls { + private val _mode = MutableStateFlow(Mode.CHANGE_TILE) + val mode: StateFlow + get() = _mode + val modeValue: Mode + get() = _mode.value + fun setMode(mode: Mode) { + _mode.value = mode + } +} + +enum class Mode { + CHANGE_TILE, + ADD_CAR +} \ No newline at end of file diff --git a/src/main/resources/4way.png b/src/main/resources/4way.png new file mode 100644 index 0000000..c395e4d Binary files /dev/null and b/src/main/resources/4way.png differ diff --git a/src/main/resources/all-but-east.png b/src/main/resources/all-but-east.png new file mode 100644 index 0000000..30066fd Binary files /dev/null and b/src/main/resources/all-but-east.png differ diff --git a/src/main/resources/all-but-north.png b/src/main/resources/all-but-north.png new file mode 100644 index 0000000..6fe3d6f Binary files /dev/null and b/src/main/resources/all-but-north.png differ diff --git a/src/main/resources/all-but-south.png b/src/main/resources/all-but-south.png new file mode 100644 index 0000000..bfaa955 Binary files /dev/null and b/src/main/resources/all-but-south.png differ diff --git a/src/main/resources/all-but-west.png b/src/main/resources/all-but-west.png new file mode 100644 index 0000000..04c2dc1 Binary files /dev/null and b/src/main/resources/all-but-west.png differ diff --git a/src/main/resources/car.png b/src/main/resources/car.png new file mode 100644 index 0000000..569e525 Binary files /dev/null and b/src/main/resources/car.png differ diff --git a/src/main/resources/horizontal.png b/src/main/resources/horizontal.png new file mode 100644 index 0000000..737c6db Binary files /dev/null and b/src/main/resources/horizontal.png differ diff --git a/src/main/resources/north-east.png b/src/main/resources/north-east.png new file mode 100644 index 0000000..5574b89 Binary files /dev/null and b/src/main/resources/north-east.png differ diff --git a/src/main/resources/north-west.png b/src/main/resources/north-west.png new file mode 100644 index 0000000..763ef26 Binary files /dev/null and b/src/main/resources/north-west.png differ diff --git a/src/main/resources/sample.png b/src/main/resources/sample.png new file mode 100644 index 0000000..11f9527 Binary files /dev/null and b/src/main/resources/sample.png differ diff --git a/src/main/resources/south-east.png b/src/main/resources/south-east.png new file mode 100644 index 0000000..49a09cd Binary files /dev/null and b/src/main/resources/south-east.png differ diff --git a/src/main/resources/south-west.png b/src/main/resources/south-west.png new file mode 100644 index 0000000..eb1b26d Binary files /dev/null and b/src/main/resources/south-west.png differ diff --git a/src/main/resources/vertical.png b/src/main/resources/vertical.png new file mode 100644 index 0000000..7e99de0 Binary files /dev/null and b/src/main/resources/vertical.png differ