Published
Edited
Jan 22, 2022
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function CodeEditor (code, height) {

// Instanciation de l'éditeur
const { editor, view } = ace(
code,
{ fontSize: "0.8em", height: (height || 260) + "px" },
{
theme: "ace/theme/tomorrow",
selectionStyle: "text",
highlightActiveLine: false,
mode: "ace/mode/glsl",
showPrintMargin: false,
showGutter: false
}
);

// Valeur intiale de viewof
view.value = editor.getValue();

// Mise en place du callback qui réagit aux changements
editor.on('change', () => {
view.value = editor.getValue();

// trick to update viewof element when erasing input text
// surprisingly, deleting a character doesn't trigger the viewof input event listener
// (it was a bit painful to spend so much time on finding how to fix this)
view.dispatchEvent(new Event("input"));
});

return view;
}
Insert cell
Insert cell
function SimplifiedShader (code, w, h, play, imageURL) {
return fragment(`
precision mediump float;

uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform sampler2D u_texture;

void main()
{
vec4 color = vec4(0,0,0,1);
vec2 pixel = gl_FragCoord.xy;
${code
.replaceAll('resolution','u_resolution')
.replaceAll('time','u_time')
.replaceAll('mouse','u_mouse')
.replaceAll('image','u_texture')}
gl_FragColor = color;
}`,{
w: Math.min(width, w || 600),
h: h || 300,
play: false,
u_texture: imageURL,
})
}
Insert cell
Insert cell
loadTexture = (gl, url) => new Promise((resolve, reject) => {
const image = new Image
image.crossOrigin = "anonymous"
image.onerror = reject
image.onload = () => {
const size = 2048
const context = DOM.context2d(size, size, 1)
context.canvas.style = "height: 60px; display: block;"
context.scale(1, -1)
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight, 0, -size, size, size)

const texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, context.canvas)
resolve(texture)
}
image.src = url
})
Insert cell
async function fragment(fragmentString, options = {}) {
let {
w = width,
h = 400,
background = 'black',
play = false,
logShader = false,
resolution = 1,
u_texture
} = options

if(logShader) return fragmentString

const canvas = DOM.canvas(w * devicePixelRatio * resolution | 0, h * devicePixelRatio * resolution | 0)
if(w === width) {
w += 28
canvas.width = w * devicePixelRatio
}

canvas.style = `
width: ${w}px;
height: ${h}px;
background: ${background};
`
const gl = canvas.value = canvas.getContext('webgl')

const vertexShader = createShader(gl, gl.VERTEX_SHADER, `
precision mediump float;

attribute vec2 a_position;
uniform vec2 u_resolution;

void main() {
gl_Position = vec4((a_position / u_resolution * 2. - 1.) * vec2(1, -1), 0., 1.);
}`)

const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentString)

// create a shader program (= vertex shader + fragment shader)
const program = createProgram(gl, vertexShader, fragmentShader)

// create a buffer to hold vertex positions
const vertexBuffer = createVertexBuffer(gl)

// store uniforms and attributes locations
const a_positionLoc = gl.getAttribLocation(program, 'a_position')
const u_timeLoc = gl.getUniformLocation(program, 'u_time')
const u_resolutionLoc = gl.getUniformLocation(program, 'u_resolution')
const u_mouseLoc = gl.getUniformLocation(program, 'u_mouse')

if (u_texture) u_texture = await loadTexture(gl, u_texture)

// update mouse on 'mousemove'
canvas.addEventListener('mousemove', e => {
gl.uniform2f(u_mouseLoc, e.offsetX * devicePixelRatio * resolution | 0, (h - e.offsetY) * devicePixelRatio * resolution | 0)
})

let startTime = Date.now()
let pauseTimestamp = Date.now()

canvas.addEventListener('click', e => {
play = !play
if(!play) pauseTimestamp = Date.now()
else {
startTime += Date.now() - pauseTimestamp
}
})

let isAnimated = (RegExp(`\\bu_time`, 'g')).test(fragmentString) || (RegExp(`\\bu_mouse`, 'g')).test(fragmentString)
let firstFrame = true;
// rendering loop
function* rendering() {
while(isAnimated || firstFrame) {
firstFrame = false;
// set viewport before drawing
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)

// define what program for drawing(rendering)
gl.useProgram(program)

// update u_time and u_resolution uniforms
const time = (Date.now() - startTime) / 1000
gl.uniform1f(u_timeLoc, time)
gl.uniform2f(u_resolutionLoc, gl.canvas.width, gl.canvas.height)

if (u_texture) gl.bindTexture(gl.TEXTURE_2D, u_texture)

// clear the canvas before we start drawing on it.
gl.clearColor(1.0, 1.0, Math.sin(time) / 2.0 + 0.5, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

// set vertexBuffer to 'a_position' attribute
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer) // bind vertexBuffer to ARRAY_BUFFER
gl.enableVertexAttribArray(a_positionLoc) // enable individual attributes
const numComponents = 2 // pull out 2 values from vertexBuffer per iteration (=per vertex)
const type = gl.FLOAT // the data in the buffer is 32bit floats
const normalize = false // don't normalize
const stride = 0 // how many bytes to get from one set of values to the next
const offset = 0 // how many bytes inside the buffer to start from
gl.vertexAttribPointer(a_positionLoc, numComponents, type, normalize, stride, offset) // Bind the buffer currently bound to gl.ARRAY_BUFFER to a generic vertex attribute

// make a draw call
const primitiveType = gl.TRIANGLE_STRIP // set how the vertices should connect
const count = 4 // specify the number of indices (vertices) to be rendered
gl.drawArrays(primitiveType, offset, count) // Render primitives from array data
yield canvas
while(!play) yield canvas
}
}
function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect();
var elemTop = rect.top;
var elemBottom = rect.bottom;
// Only completely visible elements return true:
var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
// Partially visible elements return true:
//isVisible = elemTop < window.innerHeight && elemBottom >= 0;
return isVisible;
}
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}

// start rendering loop
return rendering()
}
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more