├── .gitignore ├── Images ├── volume-screenshot-1.png ├── volume-screenshot-2.png ├── volume-screenshot-3.png ├── volume-screenshot-4.png └── volume-screenshot-5.png ├── Src ├── viewportzoom.js ├── volumerenderer.vert ├── math.js ├── opacitymap │ ├── style.css │ └── opacitymap.js ├── style.css ├── shaderutils.js ├── cameraroll.js ├── window.js ├── mousehandler.js ├── bootstrap │ └── css │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.css │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ ├── bootstrap-utilities.min.css.map │ │ ├── bootstrap-reboot.min.css.map │ │ └── bootstrap-reboot.rtl.min.css.map ├── viewport.js ├── app.js ├── index.html ├── volumerenderer.frag └── volumerenderer.js ├── Readme.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.raw 3 | /.vs/ 4 | /Temp/ 5 | /Src/datasets/ -------------------------------------------------------------------------------- /Images/volume-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/termijn/webgl-volumerendering/HEAD/Images/volume-screenshot-1.png -------------------------------------------------------------------------------- /Images/volume-screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/termijn/webgl-volumerendering/HEAD/Images/volume-screenshot-2.png -------------------------------------------------------------------------------- /Images/volume-screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/termijn/webgl-volumerendering/HEAD/Images/volume-screenshot-3.png -------------------------------------------------------------------------------- /Images/volume-screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/termijn/webgl-volumerendering/HEAD/Images/volume-screenshot-4.png -------------------------------------------------------------------------------- /Images/volume-screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/termijn/webgl-volumerendering/HEAD/Images/volume-screenshot-5.png -------------------------------------------------------------------------------- /Src/viewportzoom.js: -------------------------------------------------------------------------------- 1 | function ViewportZoom() { 2 | 3 | this.mouseWheel = function(args) { 4 | var viewport = args.viewport; 5 | viewport.setFieldOfView( 6 | Math.min(Math.PI / 180 * 120, Math.max(Math.PI / 180 * 5, viewport.fieldOfView * Math.pow(2, -args.wheelDistance / 10.0))) 7 | ); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Web-gl based volume rendering 2 | 3 | The repository contains contains a WebGL 2.0 based interactive voxel volume renderer using path tracing. It runs in modern browsers. 4 | 5 | ![](Images/volume-screenshot-1.png) 6 | ![](Images/volume-screenshot-2.png) 7 | ![](Images/volume-screenshot-3.png) 8 | ![](Images/volume-screenshot-4.png) 9 | ![](Images/volume-screenshot-5.png) -------------------------------------------------------------------------------- /Src/volumerenderer.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | layout(location=0) in vec3 position; 4 | 5 | uniform mat4 modelToWorld; 6 | uniform mat4 worldToCamera; 7 | uniform mat4 cameraToClipSpace; 8 | 9 | uniform mat4 worldToModel; 10 | uniform mat4 cameraToWorld; 11 | uniform mat4 clipSpaceToCamera; 12 | 13 | out vec3 vray_dir; 14 | out vec3 transformed_eye; 15 | 16 | void main(void) { 17 | mat4 modelToClipSpace = cameraToClipSpace * worldToCamera * modelToWorld; 18 | mat4 clipSpaceToModel = worldToModel * cameraToWorld * clipSpaceToCamera; 19 | 20 | gl_Position = modelToClipSpace * vec4(position, 1); 21 | 22 | transformed_eye = (clipSpaceToModel * vec4(0,0,-1,1)).xyz; 23 | vray_dir = position - transformed_eye; 24 | } -------------------------------------------------------------------------------- /Src/math.js: -------------------------------------------------------------------------------- 1 | function Point(x, y) { 2 | this.x = x; 3 | this.y = y; 4 | this.left = x; 5 | this.top = y; 6 | 7 | this.subtract = function(other) { 8 | return new Point(this.x - other.x, this.y - other.y); 9 | } 10 | 11 | this.add = function(other) { 12 | return new Point(this.x + other.x, this.y + other.y); 13 | } 14 | 15 | this.len = function() { 16 | return Math.sqrt(this.x * this.x + this.y + this.y); 17 | } 18 | 19 | this.inverse = function() { 20 | return new Point(-this.x, -this.y); 21 | } 22 | } 23 | 24 | function subtract(p1, p2) { 25 | return {x: p1.x - p2.x, y: p1.y - p2.y} 26 | } 27 | 28 | function clamp(value, min, max) { 29 | return Math.max(min, Math.min(value, max)); 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Martijn van Geloof 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Src/opacitymap/style.css: -------------------------------------------------------------------------------- 1 | #windowOpacityMap { 2 | position: absolute; 3 | z-index: 9; 4 | background-color: #333; 5 | border: 1px solid #555; 6 | text-align: center; 7 | height: 300px; 8 | width: 500px; 9 | border-radius: 5px; 10 | opacity: 0.9; 11 | visibility: hidden; 12 | } 13 | 14 | #opacityMapEditor > canvas { 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | .opacitySample { 20 | position: absolute; 21 | width: 20px; 22 | height: 20px; 23 | border-radius: 10px; 24 | background-color: #2196F3; 25 | cursor: grab; 26 | } 27 | 28 | .opacityValue { 29 | margin: 40px; 30 | white-space: nowrap; 31 | } 32 | 33 | #windowOpacityMapHeader { 34 | padding: 10px; 35 | cursor: move; 36 | z-index: 10; 37 | background-color: #2196F3; 38 | color: #fff; 39 | text-align: left; 40 | } 41 | 42 | #opacityMapEditor { 43 | position: absolute; 44 | background-color: #555; 45 | 46 | left: 10px; 47 | right: 10px; 48 | top: 55px; 49 | bottom: 10px; 50 | } 51 | 52 | #windowOpacityMapHeader > button { 53 | position: absolute; 54 | right: 6px; 55 | top: 6px; 56 | } -------------------------------------------------------------------------------- /Src/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0 !important; 3 | padding: 0 !important; 4 | font-family: 'Noto Serif', serif; 5 | font-size: 12px; 6 | background-color: #222; 7 | overflow: hidden; 8 | } 9 | 10 | #labelFps { 11 | display: block; 12 | position: absolute; 13 | left: 6em; 14 | top: 1em; 15 | font-size: 3em; 16 | color: #777; 17 | } 18 | 19 | .dataset-thumbnail * { 20 | text-align: center; 21 | } 22 | 23 | .form-range { 24 | width: 100%; 25 | } 26 | 27 | .progress { 28 | display: block; 29 | width: 50vw; 30 | display: block; 31 | position: absolute; 32 | left: 25vw; 33 | top: 50vh; 34 | } 35 | 36 | #datasetselector { 37 | position: absolute; 38 | padding: 1em; 39 | left: 1em; 40 | top: 1em; 41 | width: 15em; 42 | } 43 | 44 | #viewport { 45 | position: absolute; 46 | left: 0; 47 | top: 0; 48 | width: 100%; 49 | height: 100%; 50 | z-index: -1000; 51 | } 52 | 53 | #imgScreenshot { 54 | width: 250px; 55 | height: 250px; 56 | object-fit: cover; 57 | } 58 | 59 | .title { 60 | position: absolute; 61 | right:1em; 62 | top:1em; 63 | width: 20em; 64 | height: 25em; 65 | padding: 1em; 66 | } 67 | 68 | #title_background { 69 | background-color: #AAA; 70 | opacity: 0.7; 71 | border-radius: 0.5em; 72 | } 73 | 74 | #title_text { 75 | color: #FFF; 76 | } -------------------------------------------------------------------------------- /Src/shaderutils.js: -------------------------------------------------------------------------------- 1 | var shaderutils = { 2 | createShaderProgram: function(gl, vertexShaderSource, fragmentShaderSource) { 3 | const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vertexShaderSource); 4 | const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); 5 | 6 | const shaderProgram = gl.createProgram(); 7 | gl.attachShader(shaderProgram, vertexShader); 8 | gl.attachShader(shaderProgram, fragmentShader); 9 | gl.linkProgram(shaderProgram); 10 | 11 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 12 | console.log('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram)); 13 | return null; 14 | } 15 | 16 | return shaderProgram; 17 | }, 18 | 19 | loadShader: function(gl, type, source) { 20 | const shader = gl.createShader(type); 21 | 22 | gl.shaderSource(shader, source); 23 | gl.compileShader(shader); 24 | 25 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 26 | alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); 27 | gl.deleteShader(shader); 28 | return null; 29 | } 30 | 31 | return shader; 32 | }, 33 | loadShaderSource: function(url) { 34 | return new Promise((resolve, reject) => { 35 | var request = new XMLHttpRequest(); 36 | request.open('GET', url, true); 37 | request.setRequestHeader('Cache-Control', 'no-cache'); 38 | request.addEventListener('load', function() { 39 | resolve(request.responseText); 40 | }); 41 | request.send(); 42 | })} 43 | } -------------------------------------------------------------------------------- /Src/cameraroll.js: -------------------------------------------------------------------------------- 1 | var vec3 = glMatrix.vec3; 2 | var mat4 = glMatrix.mat4; 3 | 4 | function CameraRoll() { 5 | this.rotationPoint = vec3.fromValues(0, 0, 0); 6 | 7 | this.mouseDown = function(args) { 8 | this.startPosition = args.position; 9 | } 10 | 11 | this.mouseMove = function(args) { 12 | if (!args.isMouseDown) return; 13 | 14 | var worldToCamera = mat4.create(); 15 | mat4.invert(worldToCamera, args.viewport.cameraToWorld); 16 | 17 | var delta = args.position.subtract(this.startPosition).inverse(); 18 | 19 | var translationToWorldOrigin = vec3.create(); 20 | mat4.getTranslation(translationToWorldOrigin, worldToCamera); 21 | mat4.translate(args.viewport.cameraToWorld, args.viewport.cameraToWorld, translationToWorldOrigin); 22 | 23 | var xRay = vec3.fromValues(1,0,0); 24 | var xRayO = vec3.fromValues(0,0,0); 25 | vec3.subtract(xRay, xRay, xRayO); 26 | mat4.rotate( 27 | args.viewport.cameraToWorld, 28 | args.viewport.cameraToWorld, 29 | delta.y * 0.007, 30 | xRay); 31 | 32 | var yRay = vec3.fromValues(0,1,0); 33 | var yRayO = vec3.fromValues(0,0,0); 34 | vec3.subtract(yRay, yRay, yRayO); 35 | mat4.rotate( 36 | args.viewport.cameraToWorld, 37 | args.viewport.cameraToWorld, 38 | delta.x * 0.007, 39 | yRay); 40 | 41 | var translationToCamera = vec3.fromValues(-translationToWorldOrigin[0], -translationToWorldOrigin[1], -translationToWorldOrigin[2]); 42 | mat4.translate(args.viewport.cameraToWorld, args.viewport.cameraToWorld, translationToCamera); 43 | 44 | this.startPosition = args.position; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /Src/window.js: -------------------------------------------------------------------------------- 1 | function draggableWindow(element) { 2 | 3 | this.dragElement = function(elmnt) { 4 | var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; 5 | if (document.getElementById(elmnt.id + "Header")) { 6 | document.getElementById(elmnt.id + "Header").onmousedown = dragMouseDown; 7 | } else { 8 | elmnt.onmousedown = dragMouseDown; 9 | } 10 | 11 | function dragMouseDown(e) { 12 | e = e || window.event; 13 | e.preventDefault(); 14 | pos3 = e.clientX; 15 | pos4 = e.clientY; 16 | document.onmouseup = closeDragElement; 17 | document.onmousemove = elementDrag; 18 | } 19 | 20 | function elementDrag(e) { 21 | e = e || window.event; 22 | e.preventDefault(); 23 | 24 | pos1 = pos3 - e.clientX; 25 | pos2 = pos4 - e.clientY; 26 | pos3 = e.clientX; 27 | pos4 = e.clientY; 28 | 29 | elmnt.style.top = clamp(elmnt.offsetTop - pos2, 0, window.innerHeight - elmnt.offsetHeight) + "px"; 30 | elmnt.style.left = Math.max(0, Math.min(elmnt.offsetLeft - pos1, window.innerWidth - elmnt.offsetWidth)) + "px"; 31 | } 32 | 33 | function closeDragElement() { 34 | document.onmouseup = null; 35 | document.onmousemove = null; 36 | } 37 | } 38 | 39 | dragElement(element); 40 | 41 | element.style.left = window.innerWidth - element.offsetWidth; 42 | element.style.top = window.innerHeight - element.offsetHeight; 43 | 44 | var button = element.querySelector('#windowOpacityMapHeader > button'); 45 | if (button) { 46 | button.addEventListener('click', function() { 47 | element.style.visibility = 'hidden'; 48 | }); 49 | } 50 | } -------------------------------------------------------------------------------- /Src/mousehandler.js: -------------------------------------------------------------------------------- 1 | const LEFTMOUSEBUTTON = 0; 2 | const MIDDLEMOUSEBUTTON = 1; 3 | const RIGHTMOUSEBUTTON = 2; 4 | 5 | // Returns +1 for a single wheel roll 'up', -1 for a single roll 'down' 6 | function wheelDistance(evt) { 7 | if (!evt) evt = event; 8 | var w=evt.wheelDelta, d=evt.detail; 9 | if (d) { 10 | if (w) return w/d/40*d>0?1:-1; // Opera 11 | else return -d/3; // Firefox; 12 | } else return w/120; // IE/Safari/Chrome 13 | }; 14 | 15 | function MouseHandler(viewport) { 16 | this.interactions = [new ViewportZoom(), new CameraRoll()]; 17 | 18 | this.subscribe = function() { 19 | var self = this; 20 | viewport.canvas.addEventListener('mousedown', function(e) { 21 | e.preventDefault (); 22 | var e = window.event || e; 23 | 24 | const args = { 25 | button: e.button, 26 | position: new Point(e.clientX, e.clientY), 27 | source: e 28 | } 29 | 30 | if (args.button == LEFTMOUSEBUTTON) { 31 | this.isMouseDown = true; 32 | 33 | self.interactions.forEach(interaction => { 34 | if (interaction.mouseDown) interaction.mouseDown(args); 35 | viewport.setMaxResolution(1); 36 | }); 37 | viewport.setMaxResolution(1); 38 | viewport.invalidate(); 39 | } 40 | }); 41 | 42 | viewport.canvas.addEventListener('mousemove', function(e) { 43 | e.preventDefault (); 44 | var e = window.event || e; 45 | const args = { 46 | position: new Point(e.clientX, e.clientY), 47 | isMouseDown: this.isMouseDown, 48 | viewport: viewport, 49 | source: e 50 | } 51 | 52 | self.interactions.forEach(interaction => { 53 | if (interaction.mouseMove) interaction.mouseMove(args); 54 | }); 55 | 56 | if (this.isMouseDown) { 57 | viewport.invalidate(); 58 | } 59 | }); 60 | 61 | viewport.canvas.addEventListener('mouseup', function(e) { 62 | e.preventDefault (); 63 | var e = window.event || e; 64 | if (e.button == LEFTMOUSEBUTTON) { 65 | this.isMouseDown = false; 66 | const args = { 67 | position: new Point(e.clientY, e.clientY), 68 | viewport: viewport, 69 | source: e 70 | } 71 | 72 | self.interactions.forEach(interaction => { 73 | if (interaction.mouseUp) interaction.mouseUp(args); 74 | }); 75 | viewport.setMaxResolution(4); 76 | } 77 | }); 78 | 79 | viewport.canvas.addEventListener('wheel', function(e) { 80 | const args = { 81 | wheelDistance: wheelDistance(e), 82 | viewport: viewport, 83 | source: e 84 | } 85 | self.interactions.forEach(interaction => { 86 | if (interaction.mouseWheel) interaction.mouseWheel(args); 87 | }); 88 | viewport.invalidate(); 89 | }); 90 | } 91 | } -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.0-beta1 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors 4 | * Copyright 2011-2020 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus{outline:dotted 1px;outline:-webkit-focus-ring-color auto 5px}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /Src/viewport.js: -------------------------------------------------------------------------------- 1 | var vec3 = glMatrix.vec3; 2 | var mat4 = glMatrix.mat4; 3 | 4 | var Viewport = function(canvas, fpsCallback) { 5 | 6 | this.setAdaptiveResolution = function(isEnabled) { 7 | this.adaptiveResolution = isEnabled; 8 | this.invalidate(); 9 | } 10 | 11 | this.invalidate = function() { 12 | this.valid = false; 13 | this.lastInvalidationTime = performance.now() / 1000; 14 | } 15 | 16 | this.setViewportSize = function(width, height) { 17 | canvas.setAttribute("width", width.toString()); 18 | canvas.setAttribute("height", height.toString()); 19 | } 20 | 21 | this.setResolution = function(level) { 22 | level = Math.min(this.maxResolution, level); 23 | if (level == this.currentResolutionLevel) return; 24 | 25 | switch(level) { 26 | case 0: this.setViewportSize(256, 256); break; 27 | case 1: this.setViewportSize(512, 512); break; 28 | case 2: this.setViewportSize(1024, 1024); break; 29 | case 3: this.setViewportSize(1200, 1200); break; 30 | case 4: this.setViewportSize(1600, 1600); break; 31 | } 32 | this.currentResolutionLevel = level; 33 | this.invalidate(); 34 | } 35 | 36 | this.resize = function() { 37 | this.setFieldOfView(this.fieldOfView); 38 | this.invalidate(); 39 | } 40 | 41 | this.setFieldOfView = function(angleInRadians) { 42 | this.fieldOfView = angleInRadians; 43 | this.projectionMatrix = glMatrix.mat4.create(); 44 | const aspect = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight; 45 | const zNear = 1; 46 | const zFar = -400; 47 | 48 | glMatrix.mat4.perspective( 49 | this.projectionMatrix, 50 | angleInRadians, 51 | aspect, 52 | zNear, 53 | zFar); 54 | this.invalidate(); 55 | } 56 | 57 | this.setMaxResolution = function(level) { 58 | this.maxResolution = level; 59 | this.setResolution(this.currentResolutionLevel); 60 | } 61 | 62 | this.start = function() { 63 | var self = this; 64 | requestAnimationFrame(function() { self.draw(); }); 65 | } 66 | 67 | this.draw = function() { 68 | var self = this; 69 | 70 | var now = performance.now() / 1000; 71 | var delta = now - self.lastDrawTime; 72 | if (delta >= 0.5) { 73 | var fps = self.nrFrames / delta; 74 | fpsCallback(Math.round(fps) + ' f/s'); 75 | self.lastDrawTime = now; 76 | self.nrFrames = 0; 77 | 78 | if (!self.valid && self.adaptiveResolution) { 79 | if (fps < 10) { 80 | self.setResolution(Math.max(self.currentResolutionLevel - 1, 0)); 81 | } else if (fps >= 30) { 82 | self.setResolution(Math.max(self.currentResolutionLevel + 1, 0)); 83 | } 84 | } 85 | } 86 | 87 | if (self.valid) { 88 | const idleTime = now - self.lastInvalidationTime; 89 | if (idleTime > 0.25 && self.adaptiveResolution) { 90 | self.setResolution(4); 91 | } 92 | } else { 93 | self.nrFrames++; 94 | 95 | self.render(); 96 | self.valid = true; 97 | } 98 | requestAnimationFrame(function() { self.draw();}); 99 | }; 100 | 101 | this.render = function() { 102 | this.gl.clearDepth(1.0); 103 | this.gl.clearColor(0.0, 0.0, 0.0, 0.0); 104 | this.gl.clear(this.gl.COLOR_BUFFER_BIT); 105 | 106 | this.renderers.forEach(renderer => { 107 | renderer.draw(this); 108 | }); 109 | } 110 | 111 | this.setCamera 112 | 113 | this.canvas = canvas; 114 | this.lastDrawTime = performance.now() / 1000; 115 | this.valid = false; 116 | this.maxResolution = 4; 117 | this.adaptiveResolution = true; 118 | this.renderers = []; 119 | 120 | this.gl = canvas.getContext("webgl2", {preserveDrawingBuffer: true}), 121 | { 122 | width, 123 | height 124 | } = canvas.getBoundingClientRect(); 125 | 126 | if (this.gl === null) { 127 | alert("Unable to initialize WebGL 2. Your browser or machine may not support it."); 128 | return; 129 | } 130 | 131 | this.cameraToWorld = glMatrix.mat4.create(); 132 | glMatrix.mat4.translate(this.cameraToWorld, this.cameraToWorld, vec3.fromValues(0, 0, 300)); 133 | 134 | this.setFieldOfView(Math.PI / 180 * 90); 135 | this.setResolution(2); 136 | } -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-reboot.rtl.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.0-beta1 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors 4 | * Copyright 2011-2020 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus{outline:dotted 1px;outline:-webkit-focus-ring-color auto 5px}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */ -------------------------------------------------------------------------------- /Src/opacitymap/opacitymap.js: -------------------------------------------------------------------------------- 1 | function OpacityMap(element) { 2 | const HUMin = -500; 3 | const HUMax = 1000 4 | var self = this; 5 | 6 | this.opacityMapElement = element; 7 | this.eventChanged = function() { }; 8 | 9 | function pixelToHU(x) { 10 | var elementWidth = self.opacityMapElement.offsetWidth; 11 | var fraction = x / elementWidth; 12 | return HUMin + fraction * (HUMax - HUMin); 13 | } 14 | 15 | function HUToPixel(hu) { 16 | var fraction = (hu - HUMin) / (HUMax - HUMin); 17 | return fraction * self.opacityMapElement.offsetWidth; 18 | } 19 | 20 | function pixelToOpacity(y) { 21 | return Math.round((1.0 - y / self.opacityMapElement.offsetHeight) * 100) / 100; 22 | } 23 | 24 | function opacityToPixel(opacityMapElement, opacity) { 25 | return (1.0 - opacity) * opacityMapElement.offsetHeight; 26 | } 27 | 28 | this.getOpacityMap = function() { 29 | var children = self.opacityMapElement.querySelectorAll('.opacitySample'); 30 | var result = []; 31 | children.forEach(child => { 32 | var hu = pixelToHU(child.offsetLeft + child.offsetWidth / 2); 33 | var opacity = pixelToOpacity(child.offsetTop + child.offsetHeight / 2); 34 | result.push({ 35 | position: hu, 36 | opacity: opacity 37 | }); 38 | }); 39 | 40 | return result.sort(function(a, b) { return a.position - b.position; } ); 41 | } 42 | 43 | this.showOpacityMap = function(points, opacityValues) { 44 | for (i = 0; i < points.length; i++) { 45 | var x = HUToPixel(points[i]); 46 | var y = opacityToPixel(this.opacityMapElement, opacityValues[i]); 47 | 48 | var sampleElement = document.createElement('div'); 49 | sampleElement.className = 'opacitySample'; 50 | this.opacityMapElement.appendChild(sampleElement); 51 | sampleElement.style.left = x - sampleElement.offsetWidth / 2 + 'px'; 52 | sampleElement.style.top = y - sampleElement.offsetHeight / 2 + 'px'; 53 | dragOpacitySample(sampleElement); 54 | } 55 | } 56 | 57 | function addOpacitySample(element) { 58 | element.onmousedown = function(e) { 59 | e = e || window.event; 60 | e.preventDefault(); 61 | 62 | var sampleElement = document.createElement('div'); 63 | sampleElement.className = 'opacitySample'; 64 | element.appendChild(sampleElement); 65 | 66 | sampleElement.style.left = e.offsetX - sampleElement.offsetWidth / 2 + 'px'; 67 | sampleElement.style.top = e.offsetY - sampleElement.offsetHeight / 2 + 'px'; 68 | 69 | dragOpacitySample(sampleElement); 70 | 71 | self.eventChanged(); 72 | } 73 | } 74 | 75 | function showSample(element) { 76 | element.innerHTML = '' + 77 | Math.trunc(pixelToHU(element.offsetLeft + element.offsetWidth / 2)) 78 | + ' => ' 79 | + pixelToOpacity(element.offsetTop + element.offsetHeight / 2) 80 | + ''; 81 | } 82 | 83 | function dragOpacitySample(element) { 84 | 85 | element.addEventListener("mouseenter", function(e) { 86 | showSample(element); 87 | }); 88 | element.addEventListener("mouseleave", function(e) { 89 | element.innerHTML = ""; 90 | }); 91 | 92 | element.onmousedown = function(e) { 93 | e = e || window.event; 94 | e.preventDefault(); 95 | e.stopPropagation(); 96 | 97 | if (e.button === 2) { 98 | e.cancelBubble = true; 99 | element.parentElement.removeChild(element); 100 | self.eventChanged(); 101 | } else if (e.button === 0) { 102 | elementStart = { x: element.offsetLeft, y: element.offsetTop }; 103 | mouseStart = { x: e.clientX, y: e.clientY }; 104 | document.onmousemove = function(e) { 105 | e = e || window.event; 106 | mouseNow = { x: e.clientX, y: e.clientY }; 107 | var delta = subtract(mouseNow, mouseStart); 108 | var position = { 109 | x: clamp(elementStart.x + delta.x, 0 - element.offsetWidth / 2, element.parentElement.offsetWidth - element.offsetWidth / 2), 110 | y: clamp(elementStart.y + delta.y, 0 - element.offsetHeight / 2, element.parentElement.offsetHeight - element.offsetHeight / 2) 111 | } 112 | 113 | element.style.left = position.x + 'px'; 114 | element.style.top = position.y + 'px'; 115 | self.eventChanged(); 116 | showSample(element); 117 | } 118 | 119 | document.onmouseup = function(e) { 120 | document.onmousemove = null; 121 | document.onmouseup = null; 122 | element.innerHTML = ""; 123 | } 124 | } 125 | } 126 | } 127 | 128 | addOpacitySample(element); 129 | } 130 | -------------------------------------------------------------------------------- /Src/app.js: -------------------------------------------------------------------------------- 1 | function load() { 2 | var self = this; 3 | 4 | draggableWindow(document.querySelector("#windowOpacityMap")); 5 | var isMouseDown = false; 6 | 7 | const canvas = document.querySelector("#viewport"); 8 | var viewport = new Viewport(canvas, function(fps) { 9 | var span = document.querySelector("#labelFps"); 10 | span.innerHTML = fps; 11 | }); 12 | var mouseHandler = new MouseHandler(viewport); 13 | mouseHandler.subscribe(); 14 | 15 | viewport.start(); 16 | 17 | window.onresize = function() { 18 | viewport.resize(); 19 | }; 20 | 21 | const sliderResolution = document.querySelector("#resolutionSlider"); 22 | const checkboxAdaptiveResolution = document.querySelector("#checkboxAdaptiveResolution"); 23 | const widthSlider = document.querySelector("#widthSlider"); 24 | const sliderSamplingRate = document.querySelector("#sliderSamplingRate"); 25 | const labelSamplingRate = document.querySelector("#labelSamplingRate"); 26 | const progressbarLoading = document.querySelector(".progress-bar"); 27 | const progressPanel = document.querySelector(".progress"); 28 | const labelWindowLevel = document.querySelector("#labelWindowLevel"); 29 | const offsetSlider = document.querySelector("#offsetSlider"); 30 | const thumbnails = document.querySelectorAll(".dataset-thumbnail"); 31 | const buttonOpacityMap = document.querySelector('#buttonOpacityMap'); 32 | 33 | offsetSlider.addEventListener('input', function(e) { 34 | var value = parseFloat(e.target.value); 35 | labelWindowLevel.innerHTML = "Window level (" + e.target.value + ")"; 36 | volume.setWindowLevel(parseFloat(value)); 37 | viewport.invalidate(); 38 | }); 39 | 40 | checkboxAdaptiveResolution.addEventListener('input', function(e) { 41 | var isChecked = checkboxAdaptiveResolution.checked; 42 | viewport.setAdaptiveResolution(isChecked); 43 | viewport.setMaxResolution() 44 | }); 45 | 46 | widthSlider.addEventListener('input', function(e) { 47 | var value = e.target.value; 48 | volume.setWindowWidth(parseFloat(value)); 49 | viewport.invalidate(); 50 | }); 51 | 52 | sliderSamplingRate.addEventListener('input', function(e) { 53 | var value = parseInt(e.target.value); 54 | switch(value) { 55 | case 0: labelSamplingRate.innerHTML = "Sampling rate (0.25x)"; volume.setSampleRate(0.25); break; 56 | case 1: labelSamplingRate.innerHTML = "Sampling rate (0.5x)"; volume.setSampleRate(0.5); break; 57 | case 2: labelSamplingRate.innerHTML = "Sampling rate (1x)"; volume.setSampleRate(1.0); break; 58 | case 3: labelSamplingRate.innerHTML = "Sampling rate (2x)"; volume.setSampleRate(2.0); break; 59 | case 4: labelSamplingRate.innerHTML = "Sampling rate (4x)"; volume.setSampleRate(4.0); break; 60 | case 5: labelSamplingRate.innerHTML = "Sampling rate (8x)"; volume.setSampleRate(8.0); break; 61 | } 62 | viewport.invalidate(); 63 | }); 64 | 65 | sliderResolution.addEventListener('input', function(e) { 66 | var level = parseInt(e.target.value); 67 | viewport.setResolution(level); 68 | }); 69 | 70 | thumbnails.forEach(function(item) { 71 | item.addEventListener('click', function(e) { 72 | var fileUrl = item.getAttribute("data-url"); 73 | var slope = item.getAttribute("data-slope"); 74 | var intercept = item.getAttribute("data-intercept"); 75 | var width = item.getAttribute("data-width"); 76 | var height = item.getAttribute("data-height"); 77 | var length = item.getAttribute("data-length"); 78 | self.volume = new VolumeRenderer(viewport.gl, fileUrl, slope, intercept, width, height, length); 79 | viewport.renderers = viewport.renderers.filter(function(value, index, arr){ 80 | return !(value instanceof VolumeRenderer); 81 | }); 82 | viewport.renderers.push(volume); 83 | }); 84 | }); 85 | 86 | progressPanel.style.visibility = 'hidden'; 87 | document.addEventListener('loading', function(e) { 88 | console.log("loading event" + e.detail); 89 | progressPanel.style.visibility = 'visible'; 90 | progressbarLoading.style.width = e.detail.toString() + '%'; 91 | progressbarLoading.setAttribute('aria-valuenow', e.detail); 92 | 93 | if (e.detail == 100) { 94 | progressPanel.style.visibility = 'hidden'; 95 | viewport.invalidate(); 96 | } 97 | }); 98 | 99 | var opacityMap = new OpacityMap(document.querySelector('#opacityMapEditor')); 100 | opacityMap.showOpacityMap([90, 228, 330, 499], [0, 0.14027,0.38847, 0.93663]) 101 | opacityMap.eventChanged = function() { 102 | var map = opacityMap.getOpacityMap(); 103 | self.volume.setOpacityMap(map); 104 | console.log(map); 105 | viewport.invalidate(); 106 | } 107 | 108 | buttonOpacityMap.addEventListener('click', function() { 109 | var windowDiv = document.querySelector('#windowOpacityMap'); 110 | windowDiv.style.left = window.innerWidth - windowDiv.offsetWidth + 'px'; 111 | windowDiv.style.top = window.innerHeight - windowDiv.offsetHeight + 'px'; 112 | windowDiv.style.visibility = 'visible'; 113 | }); 114 | } 115 | 116 | window.onload = load; -------------------------------------------------------------------------------- /Src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Volume rendering using WebGL 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 32 |
33 | 34 |
Lumbar CBCT
35 |
36 |
37 | 39 |
40 | 41 |
Thoracic CBCT
42 |
43 |
44 | 46 |
47 | 48 |
Cervical CBCT
49 |
50 |
51 | 52 | 54 |
55 | 56 |
Full Body CT
57 |
58 |
59 | 60 | 62 |
63 | 64 |
Skull CT
65 |
66 |
67 | 68 | 70 |
71 | 72 |
Vertebroplasty CT
73 |
74 |
75 |
76 |
77 | 78 | 79 | 80 |
81 | 82 |
83 |

Volumizer

84 | 85 |
86 | 87 | 88 |
89 |
90 | 91 | 92 |
93 |
94 | 95 | 96 |
97 |
98 | 99 | 100 |
101 |
102 | 103 | 104 |
105 |
106 | 107 |
108 |
109 | 110 |
111 |
112 |
113 | 114 |
115 |
Edit Opacity Map 116 | 117 |
118 |
119 | 120 |
121 |
122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-reboot.rtl.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.0-beta1 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors 4 | * Copyright 2011-2020 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | @media (prefers-reduced-motion: no-preference) { 15 | :root { 16 | scroll-behavior: smooth; 17 | } 18 | } 19 | 20 | body { 21 | margin: 0; 22 | font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 23 | font-size: 1rem; 24 | font-weight: 400; 25 | line-height: 1.5; 26 | color: #212529; 27 | background-color: #fff; 28 | -webkit-text-size-adjust: 100%; 29 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 30 | } 31 | 32 | [tabindex="-1"]:focus:not(:focus-visible) { 33 | outline: 0 !important; 34 | } 35 | 36 | hr { 37 | margin: 1rem 0; 38 | color: inherit; 39 | background-color: currentColor; 40 | border: 0; 41 | opacity: 0.25; 42 | } 43 | 44 | hr:not([size]) { 45 | height: 1px; 46 | } 47 | 48 | h6, h5, h4, h3, h2, h1 { 49 | margin-top: 0; 50 | margin-bottom: 0.5rem; 51 | font-weight: 500; 52 | line-height: 1.2; 53 | } 54 | 55 | h1 { 56 | font-size: calc(1.375rem + 1.5vw); 57 | } 58 | @media (min-width: 1200px) { 59 | h1 { 60 | font-size: 2.5rem; 61 | } 62 | } 63 | 64 | h2 { 65 | font-size: calc(1.325rem + 0.9vw); 66 | } 67 | @media (min-width: 1200px) { 68 | h2 { 69 | font-size: 2rem; 70 | } 71 | } 72 | 73 | h3 { 74 | font-size: calc(1.3rem + 0.6vw); 75 | } 76 | @media (min-width: 1200px) { 77 | h3 { 78 | font-size: 1.75rem; 79 | } 80 | } 81 | 82 | h4 { 83 | font-size: calc(1.275rem + 0.3vw); 84 | } 85 | @media (min-width: 1200px) { 86 | h4 { 87 | font-size: 1.5rem; 88 | } 89 | } 90 | 91 | h5 { 92 | font-size: 1.25rem; 93 | } 94 | 95 | h6 { 96 | font-size: 1rem; 97 | } 98 | 99 | p { 100 | margin-top: 0; 101 | margin-bottom: 1rem; 102 | } 103 | 104 | abbr[title], 105 | abbr[data-bs-original-title] { 106 | text-decoration: underline; 107 | -webkit-text-decoration: underline dotted; 108 | text-decoration: underline dotted; 109 | cursor: help; 110 | -webkit-text-decoration-skip-ink: none; 111 | text-decoration-skip-ink: none; 112 | } 113 | 114 | address { 115 | margin-bottom: 1rem; 116 | font-style: normal; 117 | line-height: inherit; 118 | } 119 | 120 | ol, 121 | ul { 122 | padding-right: 2rem; 123 | } 124 | 125 | ol, 126 | ul, 127 | dl { 128 | margin-top: 0; 129 | margin-bottom: 1rem; 130 | } 131 | 132 | ol ol, 133 | ul ul, 134 | ol ul, 135 | ul ol { 136 | margin-bottom: 0; 137 | } 138 | 139 | dt { 140 | font-weight: 700; 141 | } 142 | 143 | dd { 144 | margin-bottom: 0.5rem; 145 | margin-right: 0; 146 | } 147 | 148 | blockquote { 149 | margin: 0 0 1rem; 150 | } 151 | 152 | b, 153 | strong { 154 | font-weight: bolder; 155 | } 156 | 157 | small { 158 | font-size: 0.875em; 159 | } 160 | 161 | mark { 162 | padding: 0.2em; 163 | background-color: #fcf8e3; 164 | } 165 | 166 | sub, 167 | sup { 168 | position: relative; 169 | font-size: 0.75em; 170 | line-height: 0; 171 | vertical-align: baseline; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | sup { 179 | top: -0.5em; 180 | } 181 | 182 | a { 183 | color: #0d6efd; 184 | text-decoration: underline; 185 | } 186 | a:hover { 187 | color: #0a58ca; 188 | } 189 | 190 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 191 | color: inherit; 192 | text-decoration: none; 193 | } 194 | 195 | pre, 196 | code, 197 | kbd, 198 | samp { 199 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 200 | font-size: 1em; 201 | direction: ltr ; 202 | unicode-bidi: bidi-override; 203 | } 204 | 205 | pre { 206 | display: block; 207 | margin-top: 0; 208 | margin-bottom: 1rem; 209 | overflow: auto; 210 | font-size: 0.875em; 211 | } 212 | pre code { 213 | font-size: inherit; 214 | color: inherit; 215 | word-break: normal; 216 | } 217 | 218 | code { 219 | font-size: 0.875em; 220 | color: #d63384; 221 | word-wrap: break-word; 222 | } 223 | a > code { 224 | color: inherit; 225 | } 226 | 227 | kbd { 228 | padding: 0.2rem 0.4rem; 229 | font-size: 0.875em; 230 | color: #fff; 231 | background-color: #212529; 232 | border-radius: 0.2rem; 233 | } 234 | kbd kbd { 235 | padding: 0; 236 | font-size: 1em; 237 | font-weight: 700; 238 | } 239 | 240 | figure { 241 | margin: 0 0 1rem; 242 | } 243 | 244 | img, 245 | svg { 246 | vertical-align: middle; 247 | } 248 | 249 | table { 250 | caption-side: bottom; 251 | border-collapse: collapse; 252 | } 253 | 254 | caption { 255 | padding-top: 0.5rem; 256 | padding-bottom: 0.5rem; 257 | color: #6c757d; 258 | text-align: right; 259 | } 260 | 261 | th { 262 | text-align: inherit; 263 | text-align: -webkit-match-parent; 264 | } 265 | 266 | thead, 267 | tbody, 268 | tfoot, 269 | tr, 270 | td, 271 | th { 272 | border-color: inherit; 273 | border-style: solid; 274 | border-width: 0; 275 | } 276 | 277 | label { 278 | display: inline-block; 279 | } 280 | 281 | button { 282 | border-radius: 0; 283 | } 284 | 285 | button:focus { 286 | outline: dotted 1px; 287 | outline: -webkit-focus-ring-color auto 5px; 288 | } 289 | 290 | input, 291 | button, 292 | select, 293 | optgroup, 294 | textarea { 295 | margin: 0; 296 | font-family: inherit; 297 | font-size: inherit; 298 | line-height: inherit; 299 | } 300 | 301 | button, 302 | select { 303 | text-transform: none; 304 | } 305 | 306 | [role=button] { 307 | cursor: pointer; 308 | } 309 | 310 | select { 311 | word-wrap: normal; 312 | } 313 | 314 | [list]::-webkit-calendar-picker-indicator { 315 | display: none; 316 | } 317 | 318 | button, 319 | [type=button], 320 | [type=reset], 321 | [type=submit] { 322 | -webkit-appearance: button; 323 | } 324 | button:not(:disabled), 325 | [type=button]:not(:disabled), 326 | [type=reset]:not(:disabled), 327 | [type=submit]:not(:disabled) { 328 | cursor: pointer; 329 | } 330 | 331 | ::-moz-focus-inner { 332 | padding: 0; 333 | border-style: none; 334 | } 335 | 336 | textarea { 337 | resize: vertical; 338 | } 339 | 340 | fieldset { 341 | min-width: 0; 342 | padding: 0; 343 | margin: 0; 344 | border: 0; 345 | } 346 | 347 | legend { 348 | float: right; 349 | width: 100%; 350 | padding: 0; 351 | margin-bottom: 0.5rem; 352 | font-size: calc(1.275rem + 0.3vw); 353 | line-height: inherit; 354 | } 355 | @media (min-width: 1200px) { 356 | legend { 357 | font-size: 1.5rem; 358 | } 359 | } 360 | legend + * { 361 | clear: right; 362 | } 363 | 364 | ::-webkit-datetime-edit-fields-wrapper, 365 | ::-webkit-datetime-edit-text, 366 | ::-webkit-datetime-edit-minute, 367 | ::-webkit-datetime-edit-hour-field, 368 | ::-webkit-datetime-edit-day-field, 369 | ::-webkit-datetime-edit-month-field, 370 | ::-webkit-datetime-edit-year-field { 371 | padding: 0; 372 | } 373 | 374 | ::-webkit-inner-spin-button { 375 | height: auto; 376 | } 377 | 378 | [type=search] { 379 | outline-offset: -2px; 380 | -webkit-appearance: textfield; 381 | } 382 | 383 | [type="tel"], 384 | [type="url"], 385 | [type="email"], 386 | [type="number"] { 387 | direction: ltr; 388 | } 389 | ::-webkit-search-decoration { 390 | -webkit-appearance: none; 391 | } 392 | 393 | ::-webkit-color-swatch-wrapper { 394 | padding: 0; 395 | } 396 | 397 | ::file-selector-button { 398 | font: inherit; 399 | } 400 | 401 | ::-webkit-file-upload-button { 402 | font: inherit; 403 | -webkit-appearance: button; 404 | } 405 | 406 | output { 407 | display: inline-block; 408 | } 409 | 410 | iframe { 411 | border: 0; 412 | } 413 | 414 | summary { 415 | display: list-item; 416 | cursor: pointer; 417 | } 418 | 419 | progress { 420 | vertical-align: baseline; 421 | } 422 | 423 | [hidden] { 424 | display: none !important; 425 | } 426 | /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.0-beta1 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors 4 | * Copyright 2011-2020 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | @media (prefers-reduced-motion: no-preference) { 15 | :root { 16 | scroll-behavior: smooth; 17 | } 18 | } 19 | 20 | body { 21 | margin: 0; 22 | font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 23 | font-size: 1rem; 24 | font-weight: 400; 25 | line-height: 1.5; 26 | color: #212529; 27 | background-color: #fff; 28 | -webkit-text-size-adjust: 100%; 29 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 30 | } 31 | 32 | [tabindex="-1"]:focus:not(:focus-visible) { 33 | outline: 0 !important; 34 | } 35 | 36 | hr { 37 | margin: 1rem 0; 38 | color: inherit; 39 | background-color: currentColor; 40 | border: 0; 41 | opacity: 0.25; 42 | } 43 | 44 | hr:not([size]) { 45 | height: 1px; 46 | } 47 | 48 | h6, h5, h4, h3, h2, h1 { 49 | margin-top: 0; 50 | margin-bottom: 0.5rem; 51 | font-weight: 500; 52 | line-height: 1.2; 53 | } 54 | 55 | h1 { 56 | font-size: calc(1.375rem + 1.5vw); 57 | } 58 | @media (min-width: 1200px) { 59 | h1 { 60 | font-size: 2.5rem; 61 | } 62 | } 63 | 64 | h2 { 65 | font-size: calc(1.325rem + 0.9vw); 66 | } 67 | @media (min-width: 1200px) { 68 | h2 { 69 | font-size: 2rem; 70 | } 71 | } 72 | 73 | h3 { 74 | font-size: calc(1.3rem + 0.6vw); 75 | } 76 | @media (min-width: 1200px) { 77 | h3 { 78 | font-size: 1.75rem; 79 | } 80 | } 81 | 82 | h4 { 83 | font-size: calc(1.275rem + 0.3vw); 84 | } 85 | @media (min-width: 1200px) { 86 | h4 { 87 | font-size: 1.5rem; 88 | } 89 | } 90 | 91 | h5 { 92 | font-size: 1.25rem; 93 | } 94 | 95 | h6 { 96 | font-size: 1rem; 97 | } 98 | 99 | p { 100 | margin-top: 0; 101 | margin-bottom: 1rem; 102 | } 103 | 104 | abbr[title], 105 | abbr[data-bs-original-title] { 106 | text-decoration: underline; 107 | -webkit-text-decoration: underline dotted; 108 | text-decoration: underline dotted; 109 | cursor: help; 110 | -webkit-text-decoration-skip-ink: none; 111 | text-decoration-skip-ink: none; 112 | } 113 | 114 | address { 115 | margin-bottom: 1rem; 116 | font-style: normal; 117 | line-height: inherit; 118 | } 119 | 120 | ol, 121 | ul { 122 | padding-left: 2rem; 123 | } 124 | 125 | ol, 126 | ul, 127 | dl { 128 | margin-top: 0; 129 | margin-bottom: 1rem; 130 | } 131 | 132 | ol ol, 133 | ul ul, 134 | ol ul, 135 | ul ol { 136 | margin-bottom: 0; 137 | } 138 | 139 | dt { 140 | font-weight: 700; 141 | } 142 | 143 | dd { 144 | margin-bottom: 0.5rem; 145 | margin-left: 0; 146 | } 147 | 148 | blockquote { 149 | margin: 0 0 1rem; 150 | } 151 | 152 | b, 153 | strong { 154 | font-weight: bolder; 155 | } 156 | 157 | small { 158 | font-size: 0.875em; 159 | } 160 | 161 | mark { 162 | padding: 0.2em; 163 | background-color: #fcf8e3; 164 | } 165 | 166 | sub, 167 | sup { 168 | position: relative; 169 | font-size: 0.75em; 170 | line-height: 0; 171 | vertical-align: baseline; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | sup { 179 | top: -0.5em; 180 | } 181 | 182 | a { 183 | color: #0d6efd; 184 | text-decoration: underline; 185 | } 186 | a:hover { 187 | color: #0a58ca; 188 | } 189 | 190 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 191 | color: inherit; 192 | text-decoration: none; 193 | } 194 | 195 | pre, 196 | code, 197 | kbd, 198 | samp { 199 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 200 | font-size: 1em; 201 | direction: ltr /* rtl:ignore */; 202 | unicode-bidi: bidi-override; 203 | } 204 | 205 | pre { 206 | display: block; 207 | margin-top: 0; 208 | margin-bottom: 1rem; 209 | overflow: auto; 210 | font-size: 0.875em; 211 | } 212 | pre code { 213 | font-size: inherit; 214 | color: inherit; 215 | word-break: normal; 216 | } 217 | 218 | code { 219 | font-size: 0.875em; 220 | color: #d63384; 221 | word-wrap: break-word; 222 | } 223 | a > code { 224 | color: inherit; 225 | } 226 | 227 | kbd { 228 | padding: 0.2rem 0.4rem; 229 | font-size: 0.875em; 230 | color: #fff; 231 | background-color: #212529; 232 | border-radius: 0.2rem; 233 | } 234 | kbd kbd { 235 | padding: 0; 236 | font-size: 1em; 237 | font-weight: 700; 238 | } 239 | 240 | figure { 241 | margin: 0 0 1rem; 242 | } 243 | 244 | img, 245 | svg { 246 | vertical-align: middle; 247 | } 248 | 249 | table { 250 | caption-side: bottom; 251 | border-collapse: collapse; 252 | } 253 | 254 | caption { 255 | padding-top: 0.5rem; 256 | padding-bottom: 0.5rem; 257 | color: #6c757d; 258 | text-align: left; 259 | } 260 | 261 | th { 262 | text-align: inherit; 263 | text-align: -webkit-match-parent; 264 | } 265 | 266 | thead, 267 | tbody, 268 | tfoot, 269 | tr, 270 | td, 271 | th { 272 | border-color: inherit; 273 | border-style: solid; 274 | border-width: 0; 275 | } 276 | 277 | label { 278 | display: inline-block; 279 | } 280 | 281 | button { 282 | border-radius: 0; 283 | } 284 | 285 | button:focus { 286 | outline: dotted 1px; 287 | outline: -webkit-focus-ring-color auto 5px; 288 | } 289 | 290 | input, 291 | button, 292 | select, 293 | optgroup, 294 | textarea { 295 | margin: 0; 296 | font-family: inherit; 297 | font-size: inherit; 298 | line-height: inherit; 299 | } 300 | 301 | button, 302 | select { 303 | text-transform: none; 304 | } 305 | 306 | [role=button] { 307 | cursor: pointer; 308 | } 309 | 310 | select { 311 | word-wrap: normal; 312 | } 313 | 314 | [list]::-webkit-calendar-picker-indicator { 315 | display: none; 316 | } 317 | 318 | button, 319 | [type=button], 320 | [type=reset], 321 | [type=submit] { 322 | -webkit-appearance: button; 323 | } 324 | button:not(:disabled), 325 | [type=button]:not(:disabled), 326 | [type=reset]:not(:disabled), 327 | [type=submit]:not(:disabled) { 328 | cursor: pointer; 329 | } 330 | 331 | ::-moz-focus-inner { 332 | padding: 0; 333 | border-style: none; 334 | } 335 | 336 | textarea { 337 | resize: vertical; 338 | } 339 | 340 | fieldset { 341 | min-width: 0; 342 | padding: 0; 343 | margin: 0; 344 | border: 0; 345 | } 346 | 347 | legend { 348 | float: left; 349 | width: 100%; 350 | padding: 0; 351 | margin-bottom: 0.5rem; 352 | font-size: calc(1.275rem + 0.3vw); 353 | line-height: inherit; 354 | } 355 | @media (min-width: 1200px) { 356 | legend { 357 | font-size: 1.5rem; 358 | } 359 | } 360 | legend + * { 361 | clear: left; 362 | } 363 | 364 | ::-webkit-datetime-edit-fields-wrapper, 365 | ::-webkit-datetime-edit-text, 366 | ::-webkit-datetime-edit-minute, 367 | ::-webkit-datetime-edit-hour-field, 368 | ::-webkit-datetime-edit-day-field, 369 | ::-webkit-datetime-edit-month-field, 370 | ::-webkit-datetime-edit-year-field { 371 | padding: 0; 372 | } 373 | 374 | ::-webkit-inner-spin-button { 375 | height: auto; 376 | } 377 | 378 | [type=search] { 379 | outline-offset: -2px; 380 | -webkit-appearance: textfield; 381 | } 382 | 383 | /* rtl:raw: 384 | [type="tel"], 385 | [type="url"], 386 | [type="email"], 387 | [type="number"] { 388 | direction: ltr; 389 | } 390 | */ 391 | ::-webkit-search-decoration { 392 | -webkit-appearance: none; 393 | } 394 | 395 | ::-webkit-color-swatch-wrapper { 396 | padding: 0; 397 | } 398 | 399 | ::file-selector-button { 400 | font: inherit; 401 | } 402 | 403 | ::-webkit-file-upload-button { 404 | font: inherit; 405 | -webkit-appearance: button; 406 | } 407 | 408 | output { 409 | display: inline-block; 410 | } 411 | 412 | iframe { 413 | border: 0; 414 | } 415 | 416 | summary { 417 | display: list-item; 418 | cursor: pointer; 419 | } 420 | 421 | progress { 422 | vertical-align: baseline; 423 | } 424 | 425 | [hidden] { 426 | display: none !important; 427 | } 428 | 429 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /Src/volumerenderer.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp int; 4 | precision highp float; 5 | 6 | uniform highp sampler3D volume; 7 | uniform ivec3 volume_dims; 8 | uniform float dt_scale; 9 | uniform vec3 lightPosition; 10 | 11 | uniform float windowLevel; // defined in rescaled values (after applying rescale) 12 | uniform float windowWidth; // defined in rescaled values (after applying rescale) 13 | uniform float rescaleIntercept; 14 | uniform float rescaleSlope; 15 | 16 | // Defines the opacity mapping from HU -> Opacity. 17 | // Positions define the rescaled (e.g. HU) values. 18 | // Samples define an opacity value [0..1] for the position. 19 | // Both textures have length numberOfOpacitySamples 20 | uniform int numberOfOpacitySamples; 21 | uniform highp sampler2D opacityMapPositions; 22 | uniform highp sampler2D opacityMapSamples; 23 | 24 | // Defines the color mapping from HU -> RGB. 25 | uniform int numberOfColorSamples; 26 | uniform highp sampler2D colorMapPositions; 27 | uniform highp sampler2D colorMapSamples; 28 | 29 | in vec3 vray_dir; 30 | in vec3 transformed_eye; 31 | out vec4 color; 32 | 33 | float linearToSRGB(float x); 34 | vec2 intersectBox(vec3 orig, vec3 dir); 35 | vec3 normalAt(vec3 position); 36 | float traceLight(vec3 voxel, vec3 light); 37 | float softShadow(vec3 position); 38 | float pointlightShadow(vec3 p); 39 | vec3 applyColorMap(float value); 40 | float rescale(float value); 41 | float opacity(float value); 42 | float applyOpacityMap(float value); 43 | 44 | float colorMapPosition(int index) { 45 | float sampleSize = 1.0 / float(numberOfColorSamples); 46 | float halfSize = sampleSize / 2.0; 47 | return texture(colorMapPositions, vec2(float(index) * sampleSize + halfSize, 0.5)).r; 48 | } 49 | 50 | vec3 colorMapSample(int index) { 51 | float sampleSize = 1.0 / float(numberOfColorSamples); 52 | float halfSize = sampleSize / 2.0; 53 | return texture(colorMapSamples, vec2(float(index) * sampleSize + halfSize, 0.5)).rgb; 54 | } 55 | 56 | float opacityMapPosition(int index) { 57 | float sampleSize = 1.0 / float(numberOfOpacitySamples); 58 | float halfSize = sampleSize / 2.0; 59 | return texture(opacityMapPositions, vec2(float(index) * sampleSize + halfSize, 0.5)).r; 60 | } 61 | 62 | float opacityMapSample(int index) { 63 | float sampleSize = 1.0 / float(numberOfOpacitySamples); 64 | float halfSize = sampleSize / 2.0; 65 | return texture(opacityMapSamples, vec2(float(index) * sampleSize + halfSize, 0.5)).r; 66 | } 67 | 68 | float sampleVolume(vec3 position) { 69 | float v = texture(volume, position).r; 70 | return rescale(v); 71 | } 72 | 73 | float sampleVolumeOpacity(vec3 position) { 74 | return opacity(sampleVolume(position)); 75 | } 76 | 77 | float rescale(float value) { 78 | return rescaleSlope * value + rescaleIntercept; 79 | } 80 | 81 | float opacity(float value) { 82 | float opacity = applyOpacityMap(value); 83 | float windowFloor = windowLevel - windowWidth / 2.0; 84 | return clamp(opacity * ((value - windowFloor) / windowWidth), 0.0, 1.0); 85 | } 86 | 87 | // http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/ 88 | float wang_hash(int seed) { 89 | seed = (seed ^ 61) ^ (seed >> 16); 90 | seed *= 9; 91 | seed = seed ^ (seed >> 4); 92 | seed *= 0x27d4eb2d; 93 | seed = seed ^ (seed >> 15); 94 | return float(seed % 2147483647) / float(2147483647); 95 | } 96 | 97 | void main(void) { 98 | vec3 ray_dir = normalize(vray_dir); 99 | vec2 t_hit = intersectBox(transformed_eye, ray_dir); 100 | if (t_hit.x > t_hit.y) { 101 | discard; 102 | } 103 | t_hit.x = max(t_hit.x, 0.0); 104 | 105 | vec3 dt_vec = 1.0 / (vec3(volume_dims) * abs(ray_dir)); 106 | float dt = dt_scale * min(dt_vec.x, min(dt_vec.y, dt_vec.z)); 107 | 108 | vec4 voxelColor = vec4(0.0); 109 | float offset = wang_hash(int(gl_FragCoord.x + 1024.0 * gl_FragCoord.y)); 110 | vec3 p = transformed_eye + (t_hit.x + offset * dt) * ray_dir; 111 | 112 | for (float t = t_hit.x; t < t_hit.y; t += dt) { 113 | float val = sampleVolume(p); 114 | vec3 color = applyColorMap(val); 115 | float opacity = opacity(val); 116 | vec4 val_color = vec4(opacity * color, opacity); 117 | 118 | val_color.a = 1.0 - pow(1.0 - val_color.a, dt_scale); 119 | voxelColor.rgb += (1.0 - voxelColor.a) * val_color.a * val_color.rgb; 120 | voxelColor.a += (1.0 - voxelColor.a) * val_color.a; 121 | 122 | if (voxelColor.a >= 0.95) { 123 | break; 124 | } 125 | p += ray_dir * dt; 126 | } 127 | 128 | vec4 diffuse = vec4(0.6); 129 | vec4 ambient = vec4(0.4); 130 | vec4 specular = vec4(0.5); 131 | float shininess = 8.0; 132 | 133 | vec3 voxelNormal = normalAt(p); 134 | vec3 lightDirection = normalize(p - lightPosition); 135 | float intensity = dot(voxelNormal, lightDirection); 136 | vec4 spec = vec4(0.0); 137 | if (intensity > 0.0) { 138 | vec3 h = normalize(lightDirection + transformed_eye); 139 | float intSpec = max(dot(h, voxelNormal), 0.0); 140 | spec = specular * pow(intSpec, shininess); 141 | } 142 | 143 | float shadow = 1.0; 144 | if (voxelColor.a >= 0.20) { 145 | shadow = clamp(softShadow(p) * 2.0 + 0.1, 0.0, 1.0); 146 | } 147 | vec4 lightColor = shadow * max(intensity * diffuse + spec, ambient); 148 | 149 | color.r = linearToSRGB((voxelColor.r * lightColor.r)); 150 | color.g = linearToSRGB((voxelColor.g * lightColor.g)); 151 | color.b = linearToSRGB((voxelColor.b * lightColor.b)); 152 | color.a = voxelColor.a; 153 | } 154 | 155 | float applyOpacityMap(float value) { 156 | for(int i = 0; i < numberOfOpacitySamples; i++) { 157 | if (value <= opacityMapPosition(i)) { 158 | if (i == 0) { 159 | return opacityMapSample(0); 160 | } 161 | int prevIndex = i - 1; 162 | float delta = opacityMapPosition(i) - opacityMapPosition(prevIndex); 163 | float fraction = (value - opacityMapPosition(prevIndex)) / delta; 164 | float color = mix(opacityMapSample(prevIndex), opacityMapSample(i), fraction); 165 | return color; 166 | } 167 | } 168 | return opacityMapSample(numberOfOpacitySamples-1); 169 | } 170 | 171 | vec3 applyColorMap(float value) { 172 | for(int i = 0; i < numberOfColorSamples; i++) { 173 | float currentColorMapPosition = colorMapPosition(i); 174 | if (value <= currentColorMapPosition) { 175 | if (i == 0) { 176 | return colorMapSample(0); 177 | } 178 | int prevIndex = i - 1; 179 | float previousColorMapPosition = colorMapPosition(prevIndex); 180 | float delta = currentColorMapPosition - previousColorMapPosition; 181 | vec3 fraction = vec3((value - previousColorMapPosition) / delta); 182 | vec3 color = mix(colorMapSample(prevIndex), colorMapSample(i), fraction); 183 | return color; 184 | } 185 | } 186 | return colorMapSample(numberOfColorSamples - 1); 187 | } 188 | 189 | float pointlightShadow(vec3 p) { 190 | float pointShadow = (1.0 - (traceLight(p, lightPosition))); 191 | return pointShadow; 192 | } 193 | 194 | float softShadow(vec3 p) { 195 | float totalShadow = 0.0; 196 | vec3 voxelSize = 1.0 / vec3(volume_dims); 197 | 198 | float delta = min(voxelSize.x, min(voxelSize.y, voxelSize.z)) * 2.0; 199 | vec3 minPos = p - vec3(delta); 200 | for (float z = -delta; z < delta; z += delta) { 201 | for (float y = -delta; y < delta; y += delta) { 202 | for (float x = -delta; x < delta; x += delta) { 203 | vec3 position = p + vec3(x, y, z); 204 | float pointShadow = (1.0 - (traceLight(position, lightPosition))); 205 | totalShadow = totalShadow + pointShadow; 206 | } 207 | } 208 | } 209 | float shadow = totalShadow / 8.0; 210 | return shadow; 211 | } 212 | 213 | vec3 normalAt(vec3 position) { 214 | vec3 voxelSize = 1.0 / vec3(volume_dims); 215 | 216 | vec3 gradient = vec3( 217 | (sampleVolumeOpacity(position + vec3(voxelSize.x, 0, 0)) - sampleVolumeOpacity(position - vec3(voxelSize.x, 0, 0))) * (1.0 / (2.0 * voxelSize.x)), 218 | (sampleVolumeOpacity(position + vec3(0, voxelSize.y, 0)) - sampleVolumeOpacity(position - vec3(0, voxelSize.y, 0))) * (1.0 / (2.0 * voxelSize.y)), 219 | (sampleVolumeOpacity(position + vec3(0, 0, voxelSize.z)) - sampleVolumeOpacity(position - vec3(0, 0, voxelSize.z))) * (1.0 / (2.0 * voxelSize.z)) 220 | ); 221 | return normalize(gradient); 222 | } 223 | 224 | // vectors are defined in unit box space 225 | float traceLight(vec3 voxel, vec3 light) { 226 | vec3 voxelToLight = light - voxel; 227 | vec3 d = normalize(voxelToLight); 228 | vec3 o = voxel - 1000.0 * d; 229 | 230 | vec2 box = intersectBox(o, d); 231 | float start = length(voxel - o); 232 | 233 | vec3 voxelSize = 1.0 / vec3(volume_dims); 234 | float samplesPerVoxel = 1.0; 235 | float delta = min(voxelSize.x, min(voxelSize.y, voxelSize.z)) / samplesPerVoxel; 236 | 237 | float integrated = 0.0; 238 | for (float t = start; t < box.y; t += delta) { 239 | vec3 p = o + t * d; 240 | float val = sampleVolumeOpacity(p); 241 | integrated = integrated + val / samplesPerVoxel / 4.0; 242 | 243 | if (integrated > 0.95) { 244 | break; 245 | } 246 | } 247 | return clamp(integrated, 0.0, 1.0); 248 | } 249 | 250 | float linearToSRGB(float x) { 251 | if (x <= 0.0031308f) { 252 | return 12.92f * x; 253 | } 254 | return 1.055f * pow(x, 1.f / 2.4f) - 0.055f; 255 | } 256 | 257 | vec2 intersectBox(vec3 orig, vec3 dir) { 258 | const vec3 boxMin = vec3(0); 259 | const vec3 boxMax = vec3(1); 260 | vec3 invDir = 1.0 / dir; 261 | vec3 tmin_tmp = (boxMin - orig) * invDir; 262 | vec3 tmax_tmp = (boxMax - orig) * invDir; 263 | vec3 tmin = min(tmin_tmp, tmax_tmp); 264 | vec3 tmax = max(tmin_tmp, tmax_tmp); 265 | float t0 = max(tmin.x, max(tmin.y, tmin.z)); 266 | float t1 = min(tmax.x, min(tmax.y, tmax.z)); 267 | return vec2(t0, t1); 268 | } -------------------------------------------------------------------------------- /Src/volumerenderer.js: -------------------------------------------------------------------------------- 1 | var vec3 = glMatrix.vec3; 2 | var mat4 = glMatrix.mat4; 3 | 4 | // Converts the given integer into a half float. 5 | var toHalfFloat = (function() { 6 | var floatView = new Float32Array(1); 7 | var int32View = new Int32Array(floatView.buffer); 8 | return function toHalfFloat(val) { 9 | floatView[0] = val; 10 | var x = int32View[0]; 11 | 12 | var bits = (x >> 16) & 0x8000; 13 | var m = (x >> 12) & 0x07ff; 14 | var e = (x >> 23) & 0xff; 15 | 16 | if (e < 103) { 17 | return bits; 18 | } 19 | 20 | if (e > 142) { 21 | bits |= 0x7c00; 22 | bits |= ((e == 255) ? 0 : 1) && (x & 0x007fffff); 23 | return bits; 24 | } 25 | if (e < 113) { 26 | m |= 0x0800; 27 | bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1); 28 | return bits; 29 | } 30 | bits |= ((e - 112) << 10) | (m >> 1); 31 | bits += m & 1; 32 | return bits; 33 | }; 34 | }()); 35 | 36 | var VolumeRenderer = function(gl, fileUrl, rescaleSlope, rescaleIntercept, widthInMM, heightInMM, lengthInMM) { 37 | 38 | this.widthInMM = parseFloat(widthInMM); 39 | this.heightInMM = parseFloat(heightInMM); 40 | this.lengthInMM = parseFloat(lengthInMM); 41 | 42 | this.loadVolume = function(url) { 43 | return new Promise((resolve, reject) => { 44 | var req = new XMLHttpRequest(); 45 | req.open("GET", url, true); 46 | req.responseType = "arraybuffer"; 47 | req.onprogress = function(evt) { 48 | const event = new CustomEvent('loading', { detail: evt.loaded / evt.total * 100 }); 49 | document.dispatchEvent(event); 50 | }; 51 | req.onerror = function(evt) { 52 | console.log(evt); 53 | }; 54 | req.onload = function(evt) { 55 | var dataBuffer = req.response; 56 | if (dataBuffer) { 57 | dataBuffer = new Uint16Array(dataBuffer); 58 | 59 | // Encode data as half-floats. Javascript doesn't support Float16 arrays, so 60 | // we store it in a Uint16Array. 61 | var converted = new Uint16Array(dataBuffer.length); 62 | for (var i = 0; i < converted.length; i++) { 63 | converted[i] = toHalfFloat(dataBuffer[i]); 64 | } 65 | 66 | console.log("volume loaded"); 67 | resolve(converted); 68 | } else { 69 | alert("Unable to load buffer properly from volume?"); 70 | console.log("no buffer?"); 71 | } 72 | }; 73 | req.send(); 74 | }); 75 | } 76 | 77 | this.transform = function(v, m) { 78 | var newVector = vec3.create(); 79 | vec3.transformMat4(newV, v, m); 80 | return newVector; 81 | } 82 | 83 | this.createCubeStrip = function() { 84 | return [ 85 | 1, 1, 0, 86 | 0, 1, 0, 87 | 1, 1, 1, 88 | 0, 1, 1, 89 | 0, 0, 1, 90 | 0, 1, 0, 91 | 0, 0, 0, 92 | 1, 1, 0, 93 | 1, 0, 0, 94 | 1, 1, 1, 95 | 1, 0, 1, 96 | 0, 0, 1, 97 | 1, 0, 0, 98 | 0, 0, 0 99 | ]; 100 | } 101 | 102 | this.createVertexBuffer = function(gl, cubeStrip) { 103 | var vao = gl.createVertexArray(); 104 | gl.bindVertexArray(vao); 105 | 106 | var vbo = gl.createBuffer(); 107 | gl.bindBuffer(gl.ARRAY_BUFFER, vbo); 108 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeStrip), gl.STATIC_DRAW); 109 | 110 | gl.enableVertexAttribArray(0); 111 | gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); 112 | } 113 | 114 | this.draw = function(viewport) { 115 | if (!this.loaded) { 116 | return; 117 | } 118 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 119 | gl.useProgram(this.shaderProgram); 120 | gl.enable(gl.CULL_FACE); 121 | gl.cullFace(gl.FRONT); 122 | gl.enable(gl.BLEND); 123 | gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 124 | 125 | var translationToCenter = glMatrix.mat4.create(); 126 | glMatrix.mat4.fromTranslation(translationToCenter, vec3.fromValues(-0.5, -0.5, -0.5)); 127 | 128 | var scaling = glMatrix.mat4.create(); 129 | glMatrix.mat4.fromScaling(scaling, vec3.fromValues(this.widthInMM, this.heightInMM, this.lengthInMM)); 130 | 131 | var modelToWorld = glMatrix.mat4.create(); 132 | glMatrix.mat4.multiply(modelToWorld, translationToCenter, modelToWorld); 133 | glMatrix.mat4.multiply(modelToWorld, scaling, modelToWorld); 134 | glMatrix.mat4.multiply(modelToWorld, this.rotation, modelToWorld); 135 | 136 | var worldToCamera = glMatrix.mat4.create(); 137 | glMatrix.mat4.invert(worldToCamera, viewport.cameraToWorld); 138 | 139 | gl.uniformMatrix4fv(this.getUniformLocation("modelToWorld"), false, modelToWorld); 140 | gl.uniformMatrix4fv(this.getUniformLocation("worldToCamera"), false, worldToCamera); 141 | gl.uniformMatrix4fv(this.getUniformLocation("cameraToClipSpace"), false, viewport.projectionMatrix); 142 | 143 | var worldToModel = glMatrix.mat4.create(); 144 | glMatrix.mat4.invert(worldToModel, modelToWorld); 145 | 146 | var clipSpaceToCamera = glMatrix.mat4.create(); 147 | glMatrix.mat4.invert(clipSpaceToCamera, viewport.projectionMatrix); 148 | 149 | gl.uniformMatrix4fv(this.getUniformLocation("worldToModel"), false, worldToModel); 150 | gl.uniformMatrix4fv(this.getUniformLocation("cameraToWorld"), false, viewport.cameraToWorld); 151 | gl.uniformMatrix4fv(this.getUniformLocation("clipSpaceToCamera"), false, clipSpaceToCamera); 152 | 153 | var lightInCameraSpace = vec3.fromValues(100.0,100.0,100.0); 154 | var lightInModelSpace = vec3.create(); 155 | vec3.transformMat4(lightInModelSpace, lightInCameraSpace, viewport.cameraToWorld); 156 | vec3.transformMat4(lightInModelSpace, lightInModelSpace, worldToModel); 157 | gl.uniform3fv(this.getUniformLocation("lightPosition"), [lightInModelSpace[0], lightInModelSpace[1], lightInModelSpace[2]]); 158 | 159 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.cubeStrip.length/3); 160 | gl.finish(); 161 | } 162 | 163 | this.getUniformLocation = function(name) { 164 | return gl.getUniformLocation(this.shaderProgram, name); 165 | } 166 | 167 | this.setWindowLevel = function(value) { 168 | gl.uniform1f(this.getUniformLocation("windowLevel"), value); 169 | } 170 | 171 | this.setWindowWidth = function(value) { 172 | gl.uniform1f(this.getUniformLocation("windowWidth"), value); 173 | } 174 | 175 | this.createTexture1D = function(gl, textureId, values) { 176 | gl.useProgram(this.shaderProgram); 177 | var texture = gl.createTexture(); 178 | gl.activeTexture(textureId); 179 | gl.bindTexture(gl.TEXTURE_2D, texture); 180 | gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32F, values.length, 1); 181 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 182 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 183 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); 184 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 185 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 186 | gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); 187 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, values.length, 1, gl.RED, gl.FLOAT, values); 188 | } 189 | 190 | this.createOpacitySamplesTexture = function(gl, samples) { 191 | this.createTexture1D(gl, gl.TEXTURE2, samples); 192 | gl.uniform1i(this.getUniformLocation('opacityMapSamples'), 2); 193 | } 194 | 195 | this.createOpacityPositionsTexture = function(gl, positions) { 196 | this.createTexture1D(gl, gl.TEXTURE1, positions); 197 | gl.uniform1i(this.getUniformLocation('opacityMapPositions'), 1); 198 | gl.uniform1i(this.getUniformLocation('numberOfOpacitySamples'), positions.length); 199 | } 200 | 201 | this.createColorPositionsTexture = function(gl, positions) { 202 | this.createTexture1D(gl, gl.TEXTURE3, positions); 203 | gl.uniform1i(this.getUniformLocation('colorMapPositions'), 3); 204 | gl.uniform1i(this.getUniformLocation('numberOfColorSamples'), positions.length); 205 | } 206 | 207 | this.createVolumeTexture16 = function(gl) { 208 | gl.useProgram(this.shaderProgram); 209 | 210 | var texture = gl.createTexture(); 211 | gl.activeTexture(gl.TEXTURE0); 212 | gl.bindTexture(gl.TEXTURE_3D, texture); 213 | gl.texStorage3D(gl.TEXTURE_3D, 1, gl.R16F, this.width, this.height, this.slices); 214 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 215 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 216 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); 217 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 218 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 219 | 220 | gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); 221 | gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, 222 | this.width, this.height, this.slices, 223 | gl.RED, gl.HALF_FLOAT, this.voxels); 224 | gl.uniform1i(this.getUniformLocation("volume"), 0); 225 | } 226 | 227 | // opacityMap: [{position, opacity}] 228 | this.setOpacityMap = function(opacityMap) { 229 | var positions = new Float32Array(opacityMap.length); 230 | var samples = new Float32Array(opacityMap.length); 231 | 232 | opacityMap.forEach((element, index) => { 233 | positions[index] = element.position; 234 | samples[index] = element.opacity; 235 | }); 236 | this.createOpacityPositionsTexture(gl, positions); 237 | this.createOpacitySamplesTexture(gl, samples); 238 | } 239 | 240 | this.createOpacityMap = function() { 241 | var positions = new Float32Array([90, 228, 330, 499]); 242 | this.createOpacityPositionsTexture(gl, positions); 243 | 244 | var opacitySamples = new Float32Array([0, 0.14027,0.38847, 0.93663]); 245 | this.createOpacitySamplesTexture(gl, opacitySamples); 246 | } 247 | 248 | this.createColorMapSamplesTexture = function(gl, colors) { 249 | gl.useProgram(this.shaderProgram); 250 | var texture = gl.createTexture(); 251 | gl.activeTexture(gl.TEXTURE4); 252 | gl.bindTexture(gl.TEXTURE_2D, texture); 253 | gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16F, colors.length / 3, 1); 254 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 255 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 256 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); 257 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 258 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 259 | gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); 260 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, colors.length / 3, 1, gl.RGB, gl.FLOAT, colors); 261 | gl.uniform1i(this.getUniformLocation("colorMapSamples"), 4); 262 | } 263 | 264 | this.createColorMap = function(gl) { 265 | var positions = new Float32Array([-770.0, -144.0, 62.0, 158.0, 210.0, 259.0, 466.0, 2001.0, 3071.0]); 266 | this.createColorPositionsTexture(gl, positions); 267 | 268 | var samples = new Float32Array([ 269 | 0.79, 0.88, 0.82, 270 | 1.0, 0.78, 0.7, 271 | 0.73, 0.0, 0.03, 272 | 1.0, 0.14, 0.17, 273 | 1.0, 0.35, 0.17, 274 | 1.0, 0.64, 0.11, 275 | 0.94, 0.92, 0.73, 276 | 1.0, 1.0, 1.0, 277 | 1.0, 0.96, 0.99 278 | ]); 279 | this.createColorMapSamplesTexture(gl, samples); 280 | } 281 | 282 | this.setSampleRate = function(value) { 283 | gl.uniform1f(this.getUniformLocation("dt_scale"), 1.0 / value); 284 | } 285 | 286 | this.init = function(gl) { 287 | this.gl = gl; 288 | var self = this; 289 | self.loaded = false; 290 | 291 | var vertexShaderPromise = shaderutils.loadShaderSource("volumerenderer.vert"); 292 | var fragmentShaderPromise = shaderutils.loadShaderSource("volumerenderer.frag"); 293 | 294 | var fileRegex = /.*_(\d+)x(\d+)x(\d+)_*.*/; 295 | var m = fileUrl.match(fileRegex); 296 | var volumeDimensions = [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])]; 297 | var loadVolumePromise = this.loadVolume(fileUrl); 298 | 299 | Promise.all([vertexShaderPromise, fragmentShaderPromise, loadVolumePromise]).then((promises) => { 300 | self.vertexShader = promises[0]; 301 | self.fragmentShader = promises[1]; 302 | self.shaderProgram = shaderutils.createShaderProgram(gl, self.vertexShader, self.fragmentShader); 303 | gl.useProgram(self.shaderProgram); 304 | 305 | self.cubeStrip = self.createCubeStrip(); 306 | self.voxels = promises[2]; 307 | self.width = volumeDimensions[0]; 308 | self.height = volumeDimensions[1]; 309 | self.slices = volumeDimensions[2]; 310 | self.rotation = glMatrix.mat4.create(); 311 | 312 | self.createVertexBuffer(gl, self.cubeStrip); 313 | self.createVolumeTexture16(gl); 314 | 315 | self.createOpacityMap(); 316 | self.createColorMap(gl); 317 | 318 | var longestAxis = Math.max(self.width, Math.max(self.height, self.slices)); 319 | self.volScale = [ 320 | self.width / longestAxis, 321 | self.height / longestAxis, 322 | self.slices / longestAxis]; 323 | gl.uniform3iv(self.getUniformLocation("volume_dims"), [self.width, self.height, self.slices]); 324 | 325 | this.setSampleRate(2.0); 326 | 327 | gl.uniform1f(this.getUniformLocation("rescaleIntercept"), parseFloat(rescaleIntercept)); 328 | gl.uniform1f(this.getUniformLocation("rescaleSlope"), parseFloat(rescaleSlope)); 329 | 330 | self.setWindowWidth(1048); 331 | self.setWindowLevel(0); 332 | 333 | self.loaded = true; 334 | console.log("Renderer loaded"); 335 | }); 336 | } 337 | this.init(gl); 338 | } -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-utilities.rtl.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../scss/bootstrap-utilities.scss","../../scss/mixins/_utilities.scss","../../scss/mixins/_breakpoints.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap Utilities v5.0.0-beta1 (https://getbootstrap.com/)\n * Copyright 2011-2020 The Bootstrap Authors\n * Copyright 2011-2020 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// Configuration\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n\n// Utilities\n\n@import \"utilities/api\";\n","// Utility generator\n// Used to generate utilities & print utilities\n@mixin generate-utility($utility, $infix, $is-rfs-media-query: false) {\n $values: map-get($utility, values);\n\n // If the values are a list or string, convert it into a map\n @if type-of($values) == \"string\" or type-of(nth($values, 1)) != \"list\" {\n $values: zip($values, $values);\n }\n\n @each $key, $value in $values {\n $properties: map-get($utility, property);\n\n // Multiple properties are possible, for example with vertical or horizontal margins or paddings\n @if type-of($properties) == \"string\" {\n $properties: append((), $properties);\n }\n\n // Use custom class if present\n $property-class: if(map-has-key($utility, class), map-get($utility, class), nth($properties, 1));\n $property-class: if($property-class == null, \"\", $property-class);\n\n // State params to generate pseudo-classes\n $state: if(map-has-key($utility, state), map-get($utility, state), ());\n\n $infix: if($property-class == \"\" and str-slice($infix, 1, 1) == \"-\", str-slice($infix, 2), $infix);\n\n // Don't prefix if value key is null (eg. with shadow class)\n $property-class-modifier: if($key, if($property-class == \"\" and $infix == \"\", \"\", \"-\") + $key, \"\");\n\n @if map-get($utility, rfs) {\n // Inside the media query\n @if $is-rfs-media-query {\n $val: rfs-value($value);\n\n // Do not render anything if fluid and non fluid values are the same\n $value: if($val == rfs-fluid-value($value), null, $val);\n }\n @else {\n $value: rfs-fluid-value($value);\n }\n }\n\n $is-rtl: map-get($utility, rtl);\n\n @if $value != null {\n @if $is-rtl == false {\n /* rtl:begin:remove */\n }\n .#{$property-class + $infix + $property-class-modifier} {\n @each $property in $properties {\n #{$property}: $value if($enable-important-utilities, !important, null);\n }\n }\n\n @each $pseudo in $state {\n .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} {\n @each $property in $properties {\n #{$property}: $value if($enable-important-utilities, !important, null);\n }\n }\n }\n @if $is-rtl == false {\n /* rtl:end:remove */\n }\n }\n }\n}\n","// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n// >> breakpoint-next(sm)\n// md\n// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// md\n// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n// md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n $n: index($breakpoint-names, $name);\n @if not $n {\n @error \"breakpoint `#{$name}` not found in `#{$breakpoints}`\";\n }\n @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n $min: map-get($breakpoints, $name);\n @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width.\n// The maximum value is reduced by 0.02px to work around the limitations of\n// `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n $max: map-get($breakpoints, $name);\n @return if($max and $max > 0, $max - .02, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.\n// Useful for making responsive utilities.\n//\n// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"\" (Returns a blank string)\n// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n @if $min {\n @media (min-width: $min) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n $max: breakpoint-max($name, $breakpoints);\n @if $max {\n @media (max-width: $max) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($lower, $breakpoints);\n $max: breakpoint-max($upper, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($lower, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($upper, $breakpoints) {\n @content;\n }\n }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n $next: breakpoint-next($name, $breakpoints);\n $max: breakpoint-max($next);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($name, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($next, $breakpoints) {\n @content;\n }\n }\n}\n","// Loop over each breakpoint\n@each $breakpoint in map-keys($grid-breakpoints) {\n\n // Generate media query if needed\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n // Loop over each utility property\n @each $key, $utility in $utilities {\n // The utility can be disabled with `false`, thus check if the utility is a map first\n // Only proceed if responsive media queries are enabled or if it's the base media query\n @if type-of($utility) == \"map\" and (map-get($utility, responsive) or $infix == \"\") {\n @include generate-utility($utility, $infix);\n }\n }\n }\n}\n\n// RFS rescaling\n@media (min-width: $rfs-mq-value) {\n @each $breakpoint in map-keys($grid-breakpoints) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n @if (map-get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) {\n // Loop over each utility property\n @each $key, $utility in $utilities {\n // The utility can be disabled with `false`, thus check if the utility is a map first\n // Only proceed if responsive media queries are enabled or if it's the base media query\n @if type-of($utility) == \"map\" and map-get($utility, rfs) {\n @include generate-utility($utility, $infix, true);\n }\n }\n }\n }\n}\n\n\n// Print utilities\n@media print {\n @each $key, $utility in $utilities {\n // The utility can be disabled with `false`, thus check if the utility is a map first\n // Then check if the utility needs print styles\n @if type-of($utility) == \"map\" and map-get($utility, print) == true {\n @include generate-utility($utility, \"-print\");\n }\n }\n}\n"]} -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-utilities.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../scss/bootstrap-utilities.scss","../../scss/mixins/_utilities.scss","../../scss/mixins/_breakpoints.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap Utilities v5.0.0-beta1 (https://getbootstrap.com/)\n * Copyright 2011-2020 The Bootstrap Authors\n * Copyright 2011-2020 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// Configuration\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n\n// Utilities\n\n@import \"utilities/api\";\n","// Utility generator\n// Used to generate utilities & print utilities\n@mixin generate-utility($utility, $infix, $is-rfs-media-query: false) {\n $values: map-get($utility, values);\n\n // If the values are a list or string, convert it into a map\n @if type-of($values) == \"string\" or type-of(nth($values, 1)) != \"list\" {\n $values: zip($values, $values);\n }\n\n @each $key, $value in $values {\n $properties: map-get($utility, property);\n\n // Multiple properties are possible, for example with vertical or horizontal margins or paddings\n @if type-of($properties) == \"string\" {\n $properties: append((), $properties);\n }\n\n // Use custom class if present\n $property-class: if(map-has-key($utility, class), map-get($utility, class), nth($properties, 1));\n $property-class: if($property-class == null, \"\", $property-class);\n\n // State params to generate pseudo-classes\n $state: if(map-has-key($utility, state), map-get($utility, state), ());\n\n $infix: if($property-class == \"\" and str-slice($infix, 1, 1) == \"-\", str-slice($infix, 2), $infix);\n\n // Don't prefix if value key is null (eg. with shadow class)\n $property-class-modifier: if($key, if($property-class == \"\" and $infix == \"\", \"\", \"-\") + $key, \"\");\n\n @if map-get($utility, rfs) {\n // Inside the media query\n @if $is-rfs-media-query {\n $val: rfs-value($value);\n\n // Do not render anything if fluid and non fluid values are the same\n $value: if($val == rfs-fluid-value($value), null, $val);\n }\n @else {\n $value: rfs-fluid-value($value);\n }\n }\n\n $is-rtl: map-get($utility, rtl);\n\n @if $value != null {\n @if $is-rtl == false {\n /* rtl:begin:remove */\n }\n .#{$property-class + $infix + $property-class-modifier} {\n @each $property in $properties {\n #{$property}: $value if($enable-important-utilities, !important, null);\n }\n }\n\n @each $pseudo in $state {\n .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} {\n @each $property in $properties {\n #{$property}: $value if($enable-important-utilities, !important, null);\n }\n }\n }\n @if $is-rtl == false {\n /* rtl:end:remove */\n }\n }\n }\n}\n","// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n// >> breakpoint-next(sm)\n// md\n// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// md\n// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n// md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n $n: index($breakpoint-names, $name);\n @if not $n {\n @error \"breakpoint `#{$name}` not found in `#{$breakpoints}`\";\n }\n @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n $min: map-get($breakpoints, $name);\n @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width.\n// The maximum value is reduced by 0.02px to work around the limitations of\n// `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n $max: map-get($breakpoints, $name);\n @return if($max and $max > 0, $max - .02, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.\n// Useful for making responsive utilities.\n//\n// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"\" (Returns a blank string)\n// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n @if $min {\n @media (min-width: $min) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n $max: breakpoint-max($name, $breakpoints);\n @if $max {\n @media (max-width: $max) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($lower, $breakpoints);\n $max: breakpoint-max($upper, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($lower, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($upper, $breakpoints) {\n @content;\n }\n }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n $next: breakpoint-next($name, $breakpoints);\n $max: breakpoint-max($next);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($name, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($next, $breakpoints) {\n @content;\n }\n }\n}\n","// Loop over each breakpoint\n@each $breakpoint in map-keys($grid-breakpoints) {\n\n // Generate media query if needed\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n // Loop over each utility property\n @each $key, $utility in $utilities {\n // The utility can be disabled with `false`, thus check if the utility is a map first\n // Only proceed if responsive media queries are enabled or if it's the base media query\n @if type-of($utility) == \"map\" and (map-get($utility, responsive) or $infix == \"\") {\n @include generate-utility($utility, $infix);\n }\n }\n }\n}\n\n// RFS rescaling\n@media (min-width: $rfs-mq-value) {\n @each $breakpoint in map-keys($grid-breakpoints) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n @if (map-get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) {\n // Loop over each utility property\n @each $key, $utility in $utilities {\n // The utility can be disabled with `false`, thus check if the utility is a map first\n // Only proceed if responsive media queries are enabled or if it's the base media query\n @if type-of($utility) == \"map\" and map-get($utility, rfs) {\n @include generate-utility($utility, $infix, true);\n }\n }\n }\n }\n}\n\n\n// Print utilities\n@media print {\n @each $key, $utility in $utilities {\n // The utility can be disabled with `false`, thus check if the utility is a map first\n // Then check if the utility needs print styles\n @if type-of($utility) == \"map\" and map-get($utility, print) == true {\n @include generate-utility($utility, \"-print\");\n }\n }\n}\n"]} -------------------------------------------------------------------------------- /Src/bootstrap/css/bootstrap-reboot.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../scss/bootstrap-reboot.scss","../../scss/_reboot.scss","dist/css/bootstrap-reboot.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap Reboot v5.0.0-beta1 (https://getbootstrap.com/)\n * Copyright 2011-2020 The Bootstrap Authors\n * Copyright 2011-2020 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)\n */\n\n@import \"functions\";\n@import \"variables\";\n// Prevent the usage of custom properties since we don't add them to `:root` in reboot\n$font-family-base: $font-family-sans-serif; // stylelint-disable-line scss/dollar-variable-default\n$font-family-code: $font-family-monospace; // stylelint-disable-line scss/dollar-variable-default\n@import \"mixins\";\n@import \"reboot\";\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n font-size: $font-size-root;\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n @include font-size($font-size-base);\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: $body-text-align;\n background-color: $body-bg; // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n\n\n// Future-proof rule: in browsers that support :focus-visible, suppress the focus outline\n// on elements that programmatically receive focus but wouldn't normally show a visible\n// focus outline. In general, this would mean that the outline is only applied if the\n// interaction that led to the element receiving programmatic focus was a keyboard interaction,\n// or the browser has somehow determined that the user is primarily a keyboard user and/or\n// wants focus outlines to always be presented.\n// See https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible\n// and https://developer.paciellogroup.com/blog/2018/03/focus-visible-and-backwards-compatibility/\n\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Work around a Firefox bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n// Credit https://github.com/suitcss/base/\n\nbutton:focus {\n outline: dotted 1px;\n outline: -webkit-focus-ring-color auto 5px;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\n\nbutton,\nselect {\n text-transform: none;\n}\n\n// Set the cursor for non-`