Skip to content

Instantly share code, notes, and snippets.

@larsgw
Created September 23, 2022 09:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save larsgw/04295ca4fe0b0252a7df1f384b511fba to your computer and use it in GitHub Desktop.
Save larsgw/04295ca4fe0b0252a7df1f384b511fba to your computer and use it in GitHub Desktop.
RTI Viewer lite
<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