Created
February 3, 2022 17:20
-
-
Save miklosme/31b65cf0fbef70644012a4dcbbe5647d to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import { useEffect, useRef, useState } from "react" | |
import { addPropertyControls, ControlType } from "framer" | |
// This is an unanimated 4 color gradient code component for Framer. | |
// TODO: support animation | |
function rgbToHex(rgb) { | |
if (rgb.startsWith("rgb(")) { | |
// Choose correct separator | |
let sep = rgb.indexOf(",") > -1 ? "," : " " | |
// Turn "rgb(r,g,b)" into [r,g,b] | |
rgb = rgb.substr(4).split(")")[0].split(sep) | |
let r = (+rgb[0]).toString(16), | |
g = (+rgb[1]).toString(16), | |
b = (+rgb[2]).toString(16) | |
if (r.length == 1) r = "0" + r | |
if (g.length == 1) g = "0" + g | |
if (b.length == 1) b = "0" + b | |
return "#" + r + g + b | |
} | |
return rgb | |
} | |
function hexColorToNumber(hex) { | |
//Check if shorthand hex value was used and double the length so the conversion in normalizeColor will work. | |
if (4 === hex.length) { | |
const hexTemp = hex | |
.substr(1) | |
.split("") | |
.map((hexTemp) => hexTemp + hexTemp) | |
.join("") | |
hex = `#${hexTemp}` | |
} | |
return `0x${hex.substr(1)}` | |
} | |
function normalizeColor(hexCode) { | |
return [ | |
((hexCode >> 16) & 255) / 255, | |
((hexCode >> 8) & 255) / 255, | |
(255 & hexCode) / 255, | |
] | |
} | |
function fixColorFormat(color) { | |
return normalizeColor(hexColorToNumber(rgbToHex(color))) | |
} | |
function createMulticolorMaterial({ colors }) { | |
colors = colors.map(rgbToHex).map(hexColorToNumber).map(normalizeColor) | |
const vertex = ` | |
varying vec2 v_texcoord; | |
void main() { | |
// float time = u_time; | |
float tilt = resolution.y / 2.0 * uvNorm.y; | |
vec3 pos = vec3( | |
position.x, | |
position.y + tilt, | |
position.z | |
); | |
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); | |
v_texcoord = uv; | |
} | |
` | |
const fragment = ` | |
varying vec2 v_texcoord; | |
void main() { | |
vec3 l = mix(bl, tl, v_texcoord.t); | |
vec3 r = mix(br, tr, v_texcoord.t); | |
vec3 c = mix(l, r, v_texcoord.s); | |
gl_FragColor = vec4(c, 1); | |
} | |
` | |
const uniforms = { | |
u_time: new this.minigl.Uniform({ | |
value: 0, | |
}), | |
tl: new this.minigl.Uniform({ | |
value: colors[0], | |
type: "vec3", | |
}), | |
tr: new this.minigl.Uniform({ | |
value: colors[1], | |
type: "vec3", | |
}), | |
bl: new this.minigl.Uniform({ | |
value: colors[2], | |
type: "vec3", | |
}), | |
br: new this.minigl.Uniform({ | |
value: colors[3], | |
type: "vec3", | |
}), | |
} | |
return new this.minigl.Material(vertex, fragment, uniforms) | |
} | |
class Gradient { | |
constructor({ createMaterial, materialArgs, height }) { | |
this.height = height | |
this.playing = false | |
this.time = 1253106 | |
this.last = 0 | |
this.createMaterial = createMaterial | |
this.materialArgs = materialArgs | |
} | |
init = (el) => { | |
this.minigl = new MiniGL(el) | |
this.material = this.createMaterial(this.materialArgs) | |
this.geometry = new this.minigl.PlaneGeometry() | |
this.mesh = new this.minigl.Mesh(this.geometry, this.material) | |
this.resize() | |
// this.playing = true | |
// requestAnimationFrame(this.animate) | |
// window.addEventListener("resize", this.resize) | |
} | |
resize = () => { | |
this.width = window.innerWidth | |
this.minigl.setSize(this.width, this.height) | |
this.minigl.setOrthographicCamera() | |
this.mesh.geometry.setSize(this.width, this.height) | |
} | |
// animate = (delta) => { | |
// if (0 !== this.last && this.isStatic) { | |
// this.minigl.render() | |
// this.disconnect() | |
// return | |
// } | |
// if (!this.shouldSkipFrame(delta)) { | |
// this.time += Math.min(delta - this.last, 1e3 / 15) | |
// this.last = delta | |
// this.mesh.material.uniforms.u_time.value = this.time | |
// this.minigl.render() | |
// } | |
// if (this.playing) { | |
// requestAnimationFrame(this.animate) | |
// } | |
// } | |
shouldSkipFrame = (delta) => { | |
return ( | |
!!window.document.hidden || | |
!this.playing || | |
parseInt(delta, 10) % 2 === 0 | |
) | |
} | |
disconnect = () => { | |
// window.removeEventListener("resize", this.resize) | |
} | |
} | |
/** | |
* These annotations control how your component sizes | |
* Learn more: https://www.framer.com/docs/guides/auto-sizing | |
* | |
* @framerSupportedLayoutWidth any | |
* @framerSupportedLayoutHeight any | |
*/ | |
export default function FourColorGradient({ tl, tr, bl, br, style }) { | |
const [gradient] = useState( | |
() => | |
new Gradient({ | |
createMaterial: createMulticolorMaterial, | |
materialArgs: { colors: [tl, tr, bl, br] }, | |
height: 300, | |
}) | |
) | |
const ref = useRef(null) | |
useEffect(() => { | |
if (!ref.current) return | |
gradient.init(ref.current) | |
gradient.minigl.render() | |
}, [ref.current]) | |
return <canvas ref={ref} style={style} /> | |
} | |
FourColorGradient.defaultProps = { | |
tl: "#09F", | |
tr: "#09F", | |
bl: "#09F", | |
br: "#09F", | |
} | |
addPropertyControls(FourColorGradient, { | |
tl: { | |
title: "Top-left", | |
type: ControlType.Color, | |
}, | |
tr: { | |
title: "Top-right", | |
type: ControlType.Color, | |
}, | |
bl: { | |
title: "Bottom-left", | |
type: ControlType.Color, | |
}, | |
br: { | |
title: "Bottom-right", | |
type: ControlType.Color, | |
}, | |
}) | |
// | |
// MiniGL | |
// | |
class MiniGL { | |
constructor(canvas, width, height, debug = () => {}) { | |
this.canvas = canvas | |
this.meshes = [] | |
this.lastDebugMsg = 0 | |
this.debug = debug | |
if (width && height) { | |
this.setSize(width, height) | |
} | |
const context = this.canvas.getContext("webgl", { | |
antialias: true, | |
}) | |
this.gl = context | |
const _miniGl = this | |
Object.defineProperties(this, { | |
Material: { | |
enumerable: false, | |
value: class { | |
constructor(vertexShaders, fragments, uniforms = {}) { | |
const material = this | |
function getShaderByType(type, source) { | |
const shader = context.createShader(type) | |
return ( | |
context.shaderSource(shader, source), | |
context.compileShader(shader), | |
context.getShaderParameter( | |
shader, | |
context.COMPILE_STATUS | |
) || | |
console.error( | |
context.getShaderInfoLog(shader) | |
), | |
_miniGl.debug("Material.compileShaderSource", { | |
source: source, | |
}), | |
shader | |
) | |
} | |
function getUniformVariableDeclarations( | |
uniforms, | |
type | |
) { | |
return Object.entries(uniforms) | |
.map(([uniform, value]) => | |
value.getDeclaration(uniform, type) | |
) | |
.join("\n") | |
} | |
;(material.uniforms = uniforms), | |
(material.uniformInstances = []) | |
const prefix = | |
"\n precision highp float;\n " | |
;(material.vertexSource = `\n ${prefix}\n attribute vec4 position;\n attribute vec2 uv;\n attribute vec2 uvNorm;\n ${getUniformVariableDeclarations( | |
_miniGl.commonUniforms, | |
"vertex" | |
)}\n ${getUniformVariableDeclarations( | |
uniforms, | |
"vertex" | |
)}\n ${vertexShaders}\n `), | |
(material.Source = `\n ${prefix}\n ${getUniformVariableDeclarations( | |
_miniGl.commonUniforms, | |
"fragment" | |
)}\n ${getUniformVariableDeclarations( | |
uniforms, | |
"fragment" | |
)}\n ${fragments}\n `), | |
(material.vertexShader = getShaderByType( | |
context.VERTEX_SHADER, | |
material.vertexSource | |
)), | |
(material.fragmentShader = getShaderByType( | |
context.FRAGMENT_SHADER, | |
material.Source | |
)), | |
(material.program = context.createProgram()), | |
context.attachShader( | |
material.program, | |
material.vertexShader | |
), | |
context.attachShader( | |
material.program, | |
material.fragmentShader | |
), | |
context.linkProgram(material.program), | |
context.getProgramParameter( | |
material.program, | |
context.LINK_STATUS | |
) || | |
console.error( | |
context.getProgramInfoLog(material.program) | |
), | |
context.useProgram(material.program), | |
material.attachUniforms( | |
void 0, | |
_miniGl.commonUniforms | |
), | |
material.attachUniforms(void 0, material.uniforms) | |
} | |
//t = uniform | |
attachUniforms(name, uniforms) { | |
//n = material | |
const material = this | |
void 0 === name | |
? Object.entries(uniforms).forEach( | |
([name, uniform]) => { | |
material.attachUniforms(name, uniform) | |
} | |
) | |
: "array" == uniforms.type | |
? uniforms.value.forEach((uniform, i) => | |
material.attachUniforms( | |
`${name}[${i}]`, | |
uniform | |
) | |
) | |
: "struct" == uniforms.type | |
? Object.entries(uniforms.value).forEach( | |
([uniform, i]) => | |
material.attachUniforms( | |
`${name}.${uniform}`, | |
i | |
) | |
) | |
: (_miniGl.debug("Material.attachUniforms", { | |
name: name, | |
uniform: uniforms, | |
}), | |
material.uniformInstances.push({ | |
uniform: uniforms, | |
location: context.getUniformLocation( | |
material.program, | |
name | |
), | |
})) | |
} | |
}, | |
}, | |
Uniform: { | |
enumerable: !1, | |
value: class { | |
constructor(e) { | |
;(this.type = "float"), Object.assign(this, e) | |
;(this.typeFn = | |
{ | |
float: "1f", | |
int: "1i", | |
vec2: "2fv", | |
vec3: "3fv", | |
vec4: "4fv", | |
mat4: "Matrix4fv", | |
}[this.type] || "1f"), | |
this.update() | |
} | |
update(value) { | |
void 0 !== this.value && | |
context[`uniform${this.typeFn}`]( | |
value, | |
0 === this.typeFn.indexOf("Matrix") | |
? this.transpose | |
: this.value, | |
0 === this.typeFn.indexOf("Matrix") | |
? this.value | |
: null | |
) | |
} | |
//e - name | |
//t - type | |
//n - length | |
getDeclaration(name, type, length) { | |
const uniform = this | |
if (uniform.excludeFrom !== type) { | |
if ("array" === uniform.type) | |
return ( | |
uniform.value[0].getDeclaration( | |
name, | |
type, | |
uniform.value.length | |
) + | |
`\nconst int ${name}_length = ${uniform.value.length};` | |
) | |
if ("struct" === uniform.type) { | |
let name_no_prefix = name.replace("u_", "") | |
return ( | |
(name_no_prefix = | |
name_no_prefix.charAt(0).toUpperCase() + | |
name_no_prefix.slice(1)), | |
`uniform struct ${name_no_prefix} | |
{\n` + | |
Object.entries(uniform.value) | |
.map(([name, uniform]) => | |
uniform | |
.getDeclaration(name, type) | |
.replace(/^uniform/, "") | |
) | |
.join("") + | |
`\n} ${name}${ | |
length > 0 ? `[${length}]` : "" | |
};` | |
) | |
} | |
return `uniform ${uniform.type} ${name}${ | |
length > 0 ? `[${length}]` : "" | |
};` | |
} | |
} | |
}, | |
}, | |
PlaneGeometry: { | |
enumerable: !1, | |
value: class { | |
constructor(width, height, n, i, orientation) { | |
context.createBuffer(), | |
(this.attributes = { | |
position: new _miniGl.Attribute({ | |
target: context.ARRAY_BUFFER, | |
size: 3, | |
}), | |
uv: new _miniGl.Attribute({ | |
target: context.ARRAY_BUFFER, | |
size: 2, | |
}), | |
uvNorm: new _miniGl.Attribute({ | |
target: context.ARRAY_BUFFER, | |
size: 2, | |
}), | |
index: new _miniGl.Attribute({ | |
target: context.ELEMENT_ARRAY_BUFFER, | |
size: 3, | |
type: context.UNSIGNED_SHORT, | |
}), | |
}), | |
this.setTopology(n, i), | |
this.setSize(width, height, orientation) | |
} | |
setTopology(e = 1, t = 1) { | |
const n = this | |
;(n.xSegCount = e), | |
(n.ySegCount = t), | |
(n.vertexCount = | |
(n.xSegCount + 1) * (n.ySegCount + 1)), | |
(n.quadCount = n.xSegCount * n.ySegCount * 2), | |
(n.attributes.uv.values = new Float32Array( | |
2 * n.vertexCount | |
)), | |
(n.attributes.uvNorm.values = new Float32Array( | |
2 * n.vertexCount | |
)), | |
(n.attributes.index.values = new Uint16Array( | |
3 * n.quadCount | |
)) | |
for (let e = 0; e <= n.ySegCount; e++) | |
for (let t = 0; t <= n.xSegCount; t++) { | |
const i = e * (n.xSegCount + 1) + t | |
if ( | |
((n.attributes.uv.values[2 * i] = | |
t / n.xSegCount), | |
(n.attributes.uv.values[2 * i + 1] = | |
1 - e / n.ySegCount), | |
(n.attributes.uvNorm.values[2 * i] = | |
(t / n.xSegCount) * 2 - 1), | |
(n.attributes.uvNorm.values[2 * i + 1] = | |
1 - (e / n.ySegCount) * 2), | |
t < n.xSegCount && e < n.ySegCount) | |
) { | |
const s = e * n.xSegCount + t | |
;(n.attributes.index.values[6 * s] = i), | |
(n.attributes.index.values[6 * s + 1] = | |
i + 1 + n.xSegCount), | |
(n.attributes.index.values[6 * s + 2] = | |
i + 1), | |
(n.attributes.index.values[6 * s + 3] = | |
i + 1), | |
(n.attributes.index.values[6 * s + 4] = | |
i + 1 + n.xSegCount), | |
(n.attributes.index.values[6 * s + 5] = | |
i + 2 + n.xSegCount) | |
} | |
} | |
n.attributes.uv.update(), | |
n.attributes.uvNorm.update(), | |
n.attributes.index.update(), | |
_miniGl.debug("Geometry.setTopology", { | |
uv: n.attributes.uv, | |
uvNorm: n.attributes.uvNorm, | |
index: n.attributes.index, | |
}) | |
} | |
setSize(width = 1, height = 1, orientation = "xz") { | |
const geometry = this | |
;(geometry.width = width), | |
(geometry.height = height), | |
(geometry.orientation = orientation), | |
(geometry.attributes.position.values && | |
geometry.attributes.position.values.length === | |
3 * geometry.vertexCount) || | |
(geometry.attributes.position.values = | |
new Float32Array(3 * geometry.vertexCount)) | |
const o = width / -2, | |
r = height / -2, | |
segment_width = width / geometry.xSegCount, | |
segment_height = height / geometry.ySegCount | |
for ( | |
let yIndex = 0; | |
yIndex <= geometry.ySegCount; | |
yIndex++ | |
) { | |
const t = r + yIndex * segment_height | |
for ( | |
let xIndex = 0; | |
xIndex <= geometry.xSegCount; | |
xIndex++ | |
) { | |
const r = o + xIndex * segment_width, | |
l = | |
yIndex * (geometry.xSegCount + 1) + | |
xIndex | |
;(geometry.attributes.position.values[ | |
3 * l + "xyz".indexOf(orientation[0]) | |
] = r), | |
(geometry.attributes.position.values[ | |
3 * l + "xyz".indexOf(orientation[1]) | |
] = -t) | |
} | |
} | |
geometry.attributes.position.update(), | |
_miniGl.debug("Geometry.setSize", { | |
position: geometry.attributes.position, | |
}) | |
} | |
}, | |
}, | |
Mesh: { | |
enumerable: !1, | |
value: class { | |
constructor(geometry, material) { | |
const mesh = this | |
;(mesh.geometry = geometry), | |
(mesh.material = material), | |
(mesh.wireframe = !1), | |
(mesh.attributeInstances = []), | |
Object.entries(mesh.geometry.attributes).forEach( | |
([e, attribute]) => { | |
mesh.attributeInstances.push({ | |
attribute: attribute, | |
location: attribute.attach( | |
e, | |
mesh.material.program | |
), | |
}) | |
} | |
), | |
_miniGl.meshes.push(mesh), | |
_miniGl.debug("Mesh.constructor", { | |
mesh: mesh, | |
}) | |
} | |
draw() { | |
context.useProgram(this.material.program), | |
this.material.uniformInstances.forEach( | |
({ uniform: e, location: t }) => e.update(t) | |
), | |
this.attributeInstances.forEach( | |
({ attribute: e, location: t }) => e.use(t) | |
), | |
context.drawElements( | |
this.wireframe | |
? context.LINES | |
: context.TRIANGLES, | |
this.geometry.attributes.index.values.length, | |
context.UNSIGNED_SHORT, | |
0 | |
) | |
} | |
remove() { | |
_miniGl.meshes = _miniGl.meshes.filter((e) => e != this) | |
} | |
}, | |
}, | |
Attribute: { | |
enumerable: !1, | |
value: class { | |
constructor(e) { | |
;(this.type = context.FLOAT), | |
(this.normalized = !1), | |
(this.buffer = context.createBuffer()), | |
Object.assign(this, e), | |
this.update() | |
} | |
update() { | |
void 0 !== this.values && | |
(context.bindBuffer(this.target, this.buffer), | |
context.bufferData( | |
this.target, | |
this.values, | |
context.STATIC_DRAW | |
)) | |
} | |
attach(e, t) { | |
const n = context.getAttribLocation(t, e) | |
return ( | |
this.target === context.ARRAY_BUFFER && | |
(context.enableVertexAttribArray(n), | |
context.vertexAttribPointer( | |
n, | |
this.size, | |
this.type, | |
this.normalized, | |
0, | |
0 | |
)), | |
n | |
) | |
} | |
use(e) { | |
context.bindBuffer(this.target, this.buffer), | |
this.target === context.ARRAY_BUFFER && | |
(context.enableVertexAttribArray(e), | |
context.vertexAttribPointer( | |
e, | |
this.size, | |
this.type, | |
this.normalized, | |
0, | |
0 | |
)) | |
} | |
}, | |
}, | |
}) | |
const a = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] | |
_miniGl.commonUniforms = { | |
projectionMatrix: new _miniGl.Uniform({ | |
type: "mat4", | |
value: a, | |
}), | |
modelViewMatrix: new _miniGl.Uniform({ | |
type: "mat4", | |
value: a, | |
}), | |
resolution: new _miniGl.Uniform({ | |
type: "vec2", | |
value: [1, 1], | |
}), | |
aspectRatio: new _miniGl.Uniform({ | |
type: "float", | |
value: 1, | |
}), | |
} | |
} | |
setSize(e = 640, t = 480) { | |
;(this.width = e), | |
(this.height = t), | |
(this.canvas.width = e), | |
(this.canvas.height = t), | |
this.gl.viewport(0, 0, e, t), | |
(this.commonUniforms.resolution.value = [e, t]), | |
(this.commonUniforms.aspectRatio.value = e / t), | |
this.debug("MiniGL.setSize", { | |
width: e, | |
height: t, | |
}) | |
} | |
//left, right, top, bottom, near, far | |
setOrthographicCamera(e = 0, t = 0, n = 0, i = -2e3, s = 2e3) { | |
;(this.commonUniforms.projectionMatrix.value = [ | |
2 / this.width, | |
0, | |
0, | |
0, | |
0, | |
2 / this.height, | |
0, | |
0, | |
0, | |
0, | |
2 / (i - s), | |
0, | |
e, | |
t, | |
n, | |
1, | |
]), | |
this.debug( | |
"setOrthographicCamera", | |
this.commonUniforms.projectionMatrix.value | |
) | |
} | |
render() { | |
this.gl.clearColor(0, 0, 0, 0), | |
this.gl.clearDepth(1), | |
this.meshes.forEach((e) => e.draw()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment