Created
September 23, 2022 09:34
-
-
Save larsgw/04295ca4fe0b0252a7df1f384b511fba to your computer and use it in GitHub Desktop.
RTI Viewer lite
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<canvas id="a" width="1000" height="1000" style="border: 1px solid red;"></canvas> | |
<script type="text/javascript"> | |
function parseImageResponse (response) { | |
// Parse XML | |
const parser = new DOMParser() | |
const xml = parser.parseFromString(response, 'application/xml') | |
const multiRes = xml.getElementsByTagName('MultiRes')[0] | |
// Gather info | |
const info = { image: {}, coefficients: {} } | |
{ | |
info.image.format = multiRes.getAttribute('format') === '1' ? 'png' : 'jpg' | |
const content = multiRes.getElementsByTagName('Content')[0] | |
info.type = content.getAttribute('type') | |
info.layers = 3 | |
const size = content.getElementsByTagName('Size')[0] | |
info.image.width = size.getAttribute('width') | |
info.image.height = size.getAttribute('height') | |
info.coefficients.number = size.getAttribute('coefficients') | |
if (info.type !== 'IMAGE') { | |
const scale = content.getElementsByTagName('Scale')[0] | |
info.coefficients.scale = scale.textContent.trim().split(' ') | |
const bias = content.getElementsByTagName('Bias')[0] | |
info.coefficients.bias = bias.textContent.trim().split(' ') | |
} | |
} | |
// Build tree | |
{ | |
const tree = multiRes.getElementsByTagName('Tree')[0].textContent | |
const [treeInfo, tileSize, scale, offset, ...nodes] = tree.split('\n') | |
const [nodeCount, rootIndex] = treeInfo.split(' ') | |
info.tree = { | |
nodeCount, | |
rootIndex, | |
tileSize, | |
scale: scale.split(' '), | |
offset: offset.split(' '), | |
nodes: nodes.map(node => { | |
const tokens = node.split(' ') | |
return { | |
id: tokens[0], | |
parentIndex: tokens[1], | |
childrenIndices: tokens.slice(2, 6), | |
projectedSize: tokens[6], | |
zScale: tokens[7], | |
box: { | |
min: tokens.slice(8, 11), | |
max: tokens.slice(11, 14) | |
} | |
} | |
}) | |
} | |
// TODO custom tiler | |
} | |
return info | |
} | |
function loadImage (url) { | |
return fetch(url + '/info.xml') | |
.then(response => response.text()) | |
.then(parseImageResponse) | |
} | |
const ROOT = '/dl/ptm/P005115_o' | |
loadImage(ROOT).then(main) | |
const canvas = document.getElementById('a') | |
const gl = canvas.getContext('webgl') | |
if (gl === null) { | |
console.error('WebGL not available') | |
} | |
const vertexShader = createShader(gl, gl.VERTEX_SHADER, ` | |
attribute vec2 a_position; | |
attribute vec2 a_texCoord; | |
uniform vec2 u_resolution; | |
varying vec2 v_texCoord; | |
void main() { | |
gl_Position = vec4((2.0 * a_position / u_resolution) - 1.0, 0, 1); | |
v_texCoord = a_texCoord; | |
} | |
`) | |
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, ` | |
precision highp float; | |
uniform sampler2D u_image0; | |
uniform sampler2D u_image1; | |
uniform sampler2D u_image2; | |
uniform mat3 u_bias; | |
uniform mat3 u_scale; | |
uniform vec2 u_light; | |
varying vec2 v_texCoord; | |
void main() { | |
vec4 a0_2 = texture2D(u_image0, v_texCoord); | |
vec4 a3_5 = texture2D(u_image1, v_texCoord); | |
float a0 = (a0_2[0] - u_bias[0][0]) * u_scale[0][0]; | |
float a1 = (a0_2[1] - u_bias[0][1]) * u_scale[0][1]; | |
float a2 = (a0_2[2] - u_bias[0][2]) * u_scale[0][2]; | |
float a3 = (a3_5[0] - u_bias[1][0]) * u_scale[1][0]; | |
float a4 = (a3_5[1] - u_bias[1][1]) * u_scale[1][1]; | |
float a5 = (a3_5[2] - u_bias[1][2]) * u_scale[1][2]; | |
float lu = u_light[0]; | |
float lv = u_light[1]; | |
float l = a0 * lu * lu + a1 * lv * lv + a2 * lu * lv + a3 * lu + a4 * lv + a5; | |
vec4 rgb = texture2D(u_image2, v_texCoord); | |
gl_FragColor = vec4(l * rgb[0], l * rgb[1], l * rgb[2], 1); | |
} | |
`) | |
const program = createProgram(gl, vertexShader, fragmentShader) | |
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position') | |
const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution') | |
const texCoordLocation = gl.getAttribLocation(program, 'a_texCoord') | |
const biasLocation = gl.getUniformLocation(program, 'u_bias') | |
const scaleLocation = gl.getUniformLocation(program, 'u_scale') | |
const lightLocation = gl.getUniformLocation(program, 'u_light') | |
function main (info) { | |
const imageLocations = Array(info.layers).fill(0).map((_, i) => gl.getUniformLocation(program, 'u_image' + i)) | |
window.imageLocations = imageLocations | |
const textures = [] | |
for (let i = 0; i < info.layers; i++) { | |
const texture = gl.createTexture() | |
gl.activeTexture(gl.TEXTURE0 + i) | |
gl.bindTexture(gl.TEXTURE_2D, texture) | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) | |
textures.push(texture) | |
} | |
renderNodes([info.tree.nodes[info.tree.rootIndex]], info, {imageLocations,textures}) | |
} | |
function renderNodes (nodes, info, p) { | |
const children = [] | |
const tiles = [] | |
for (const node of nodes) { | |
const urls = Array(info.layers) | |
.fill([ROOT + '/' + node.id + '_', '.' + info.image.format]) | |
.map((parts, i) => parts.join(i + 1)) | |
tiles.push(...urls.map((url, i) => fetchImage(url).then(image => ({ | |
image, | |
layer: i, | |
size: info.tree.tileSize, | |
width: node.box.max[0] - node.box.min[0], | |
height: node.box.max[1] - node.box.min[1], | |
left: node.box.min[0], | |
top: node.box.min[1] | |
})))) | |
for (const index of node.childrenIndices) { | |
if (index !== '-1') { | |
children.push(info.tree.nodes[index]) | |
} | |
} | |
} | |
Promise.all(tiles).then(tiles => render(tiles, p)).then(() => { | |
if (children.length) { | |
return renderNodes(children, info, p) | |
} | |
}) | |
} | |
function fetchImage (url) { | |
return new Promise(resolve => { | |
const image = new Image() | |
image.src = url | |
image.onload = () => resolve(image) | |
}) | |
} | |
function createShader (gl, type, source) { | |
const shader = gl.createShader(type) | |
gl.shaderSource(shader, source) | |
gl.compileShader(shader) | |
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
return shader | |
} else { | |
console.log(gl.getShaderInfoLog(shader)) | |
gl.deleteShader(shader) | |
} | |
} | |
function createProgram (gl, vertexShader, fragmentShader) { | |
const program = gl.createProgram() | |
gl.attachShader(program, vertexShader) | |
gl.attachShader(program, fragmentShader) | |
gl.linkProgram(program) | |
if (gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
return program | |
} else { | |
console.log(gl.getProgramInfoLog(program)) | |
gl.deleteProgram(program) | |
} | |
} | |
function render (images, {imageLocations,textures}) { | |
const width = 2 + images[0].size / images[0].width | |
const height = 2 + images[0].size / images[0].height | |
for (let i = 0; i < textures.length; i++) { | |
gl.activeTexture(gl.TEXTURE0 + i) | |
gl.bindTexture(gl.TEXTURE_2D, textures[i]) | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null) | |
} | |
for (const image of images) { | |
gl.activeTexture(gl.TEXTURE0 + image.layer) | |
gl.bindTexture(gl.TEXTURE_2D, textures[image.layer]) | |
gl.texSubImage2D( | |
gl.TEXTURE_2D, | |
0, | |
width * image.left, | |
height * (1 - image.top), | |
gl.RGBA, | |
gl.UNSIGNED_BYTE, | |
image.image | |
) | |
} | |
drawScene() | |
} | |
const light = [0, 0] | |
function drawScene() { | |
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) | |
gl.clearColor(0, 0, 0, 0) | |
gl.clear(gl.COLOR_BUFFER_BIT) | |
gl.useProgram(program) | |
const positionBuffer = gl.createBuffer() | |
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | |
0, 0, | |
gl.canvas.width, 0, | |
0, gl.canvas.height, | |
0, gl.canvas.height, | |
gl.canvas.width, 0, | |
gl.canvas.width, gl.canvas.height]), gl.STATIC_DRAW) | |
gl.enableVertexAttribArray(positionAttributeLocation) | |
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0) | |
const texCoordBuffer = gl.createBuffer() | |
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer) | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | |
0.0, 0.0, | |
1.0, 0.0, | |
0.0, 1.0, | |
0.0, 1.0, | |
1.0, 0.0, | |
1.0, 1.0]), gl.STATIC_DRAW) | |
gl.enableVertexAttribArray(texCoordLocation) | |
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0) | |
imageLocations.forEach((location, i) => { gl.uniform1i(location, i) }) | |
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height) | |
gl.uniformMatrix3fv(biasLocation, false, [136, 124, 69, 75, 74, 0, 0, 0, 0].map(v => v/255)) | |
gl.uniformMatrix3fv(scaleLocation, false, [2, 2, 2, 2, 2, 2, 0, 0, 0]) | |
gl.uniform2f(lightLocation, ...light) | |
gl.drawArrays(gl.TRIANGLES, 0, 6) | |
} | |
canvas.addEventListener('mousemove', (e) => { | |
const rect = canvas.getBoundingClientRect() | |
light[0] = 2 * (e.clientX - rect.left) / gl.canvas.clientWidth - 1 | |
light[1] = 2 * (e.clientY - rect.top) / gl.canvas.clientHeight - 1 | |
requestAnimationFrame(drawScene) | |
}) | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment