Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract the generic logic into a useOgl hook #12

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 40 additions & 74 deletions core/lib/components/lumiflex/index.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,51 @@
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
import { Color, Mesh, Program, Triangle } from "ogl";
import React from "react";
import { useEffect, useRef } from "react";
import vert from "./vert.glsl";
import frag from "./frag.glsl";
import { CommonProps, ControlProps, TimeProps } from "../../types/CommonProps";
import { useOgl } from "../../hooks";

export interface LumiflexProps extends CommonProps, TimeProps, ControlProps {}

export function Lumiflex(props: LumiflexProps) {
const propsRef = useRef<LumiflexProps>(props);
const ctnDom = useRef<HTMLDivElement>(null);

useEffect(() => {
propsRef.current = props;
});

useEffect(() => {
if (!ctnDom.current) {
return;
}

const ctn = ctnDom.current;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);

function resize() {
if (ctn == null) {
return;
}

const scale = 1;
renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale);
}
window.addEventListener("resize", resize, false);
resize();

const geometry = new Triangle(gl);

const program = new Program(gl, {
vertex: vert,
fragment: frag,
uniforms: {
uTime: { value: 0 },
uColor: { value: new Color(0.3, 0.2, 0.5) },
},
const containerRef = useOgl({
props,
init: ({ gl }) => {
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vert,
fragment: frag,
uniforms: {
uTime: { value: 0 },
uColor: { value: new Color(0.3, 0.2, 0.5) },
},
});

const mesh = new Mesh(gl, { geometry, program });

return { geometry, program, mesh };
},
render: ({ time: t, props, program, renderer, mesh }) => {
const { time: time = t * 0.01, speed = 1.0 } = props.current;
program.uniforms.uTime.value = time * speed * 0.1;
renderer.render({ scene: mesh });
},
resize: ({ renderer, container }) => {
renderer.setSize(container.offsetWidth, container.offsetHeight);
},
destroy: ({ gl }) => {
gl.getExtension("WEBGL_lose_context")?.loseContext();
},
});

const mesh = new Mesh(gl, { geometry, program });

let animateId: number;

animateId = requestAnimationFrame(update);

function update(t: number) {
animateId = requestAnimationFrame(update);

const { time: time = t * 0.01, speed = 1.0 } = propsRef.current;

program.uniforms.uTime.value = time * speed * 0.1;

renderer.render({ scene: mesh });
}

ctn.appendChild(gl.canvas);
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener("resize", resize);
ctn.removeChild(gl.canvas);
gl.getExtension("WEBGL_lose_context")?.loseContext();
};
}, []);

return (
<div
ref={ctnDom}
style={{
width: "100%",
height: "100%",
}}
{...props}
/>
);
return (
<div
ref={containerRef}
style={{
width: "100%",
height: "100%",
}}
{...props}
/>
);
}
1 change: 1 addition & 0 deletions core/lib/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useOgl'
113 changes: 113 additions & 0 deletions core/lib/hooks/useOgl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { type OGLRenderingContext, Renderer } from "ogl";
import {MutableRefObject, useEffect, useRef} from "react";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export declare type AnyProps = Record<string, any>;

export declare type UseOglContext<
Props extends AnyProps,
// eslint-disable-next-line @typescript-eslint/ban-types
AdditionalContext extends AnyProps = {},
> = AdditionalContext & {
props: MutableRefObject<Props>;
gl: OGLRenderingContext;
renderer: Renderer;
container: HTMLDivElement;
};

export declare type UseOglContextWithTime<
Props extends AnyProps,
// eslint-disable-next-line @typescript-eslint/ban-types
AdditionalContext extends AnyProps = {},
> = UseOglContext<Props, AdditionalContext> & {
time: number;
};


export interface UseOglProps<
Props extends AnyProps,
InitReturnType extends AnyProps,
> {
props: Props;
init: (context: UseOglContext<Props>) => InitReturnType;
resize: (context: UseOglContext<Props, InitReturnType>) => void;
render: (context: UseOglContextWithTime<Props, InitReturnType>) => void;
destroy: (context: UseOglContext<Props, InitReturnType>) => void;
}

export function useOgl<Props extends AnyProps, InitReturnType extends AnyProps>(
options: UseOglProps<Props, InitReturnType>,
) {
const containerRef = useRef<HTMLDivElement>(null);
const propsRef = useRef<Props>(options.props);

const resizeObserver = useRef<ResizeObserver | null>(null);

const rendererRef = useRef<Renderer | null>(null);
const glRef = useRef<OGLRenderingContext | null>(null);

function onResize() {
const renderer = rendererRef.current;
const container = containerRef.current;
if (!container || !renderer) {
return;
}
renderer.setSize(container.offsetWidth, container.offsetHeight);
}

useEffect(() => {
propsRef.current = options.props;
}, [options.props]);

useEffect(() => {
const container = containerRef.current;
if (!container) {
return;
}

const renderer = (rendererRef.current = new Renderer());
const gl = (glRef.current = renderer.gl);
gl.clearColor(1, 1, 1, 1);

window.addEventListener("resize", onResize);
resizeObserver.current = new ResizeObserver(onResize);
resizeObserver.current.observe(container);
onResize();


const baseContext: UseOglContext<Props> = {
props: propsRef,
gl,
renderer,
container,
};

const initReturn = options.init(baseContext);
const withInitContext: UseOglContext<Props, InitReturnType> = {
...baseContext,
...initReturn,
};

options.resize(withInitContext);

let animateId: number;

function update(t: number) {
animateId = requestAnimationFrame(update);
options.render({ ...withInitContext, time: t });
}
animateId = requestAnimationFrame(update);

container.appendChild(gl.canvas);

return () => {
window.cancelAnimationFrame(animateId);
window.removeEventListener("resize", onResize);
container.removeChild(gl.canvas);
resizeObserver.current?.disconnect();
options.destroy(withInitContext);
};
}, []);

return containerRef;
}