import React, {useMemo} from 'react'; import type {CubicBezierHandle} from '@shopify/react-native-skia'; import { Skia, isEdge, Group, add, Canvas, ImageShader, Patch, vec, useImage, useClock, } from '@shopify/react-native-skia'; import {View, useWindowDimensions, StyleSheet} from 'react-native'; import type {SharedValue} from 'react-native-reanimated'; import Animated, { useDerivedValue, useSharedValue, } from 'react-native-reanimated'; import {GestureDetector} from 'react-native-gesture-handler'; import {createNoise2D} from '../../components/SimpleNoise'; import {symmetric} from './Math'; import {Cubic} from './Cubic'; import {Curves} from './Curves'; import {useHandles} from './useHandles'; const rectToTexture = ( vertices: CubicBezierHandle[], [tl, tr, br, bl]: number[], ) => [ vertices[tl].pos, vertices[tr].pos, vertices[br].pos, vertices[bl].pos, ] as const; const rectToColors = (colors: string[], [tl, tr, br, bl]: number[]) => [ colors[tl], colors[tr], colors[br], colors[bl], ]; const useRectToPatch = ( mesh: SharedValue, indices: number[], ): SharedValue< [CubicBezierHandle, CubicBezierHandle, CubicBezierHandle, CubicBezierHandle] > => useDerivedValue(() => { const tl = mesh.value[indices[0]]; const tr = mesh.value[indices[1]]; const br = mesh.value[indices[2]]; const bl = mesh.value[indices[3]]; return [ { pos: tl.pos, c1: tl.c2, c2: tl.c1, }, { pos: tr.pos, c1: symmetric(tr.c1, tr.pos), c2: tr.c2, }, { pos: br.pos, c1: symmetric(br.c2, br.pos), c2: symmetric(br.c1, br.pos), }, { pos: bl.pos, c1: bl.c1, c2: symmetric(bl.c2, bl.pos), }, ]; }, [mesh]); interface CoonsPatchMeshGradientProps { rows: number; cols: number; colors: string[]; debug?: boolean; lines?: boolean; handles?: boolean; play?: boolean; } const F = 10000; const A = 80; export const CoonsPatchMeshGradient = ({ rows, cols, colors, debug, lines, handles, play, }: CoonsPatchMeshGradientProps) => { const {width, height} = useWindowDimensions(); const window = useMemo( () => Skia.XYWHRect(0, 0, width, height), [height, width], ); const clock = useClock(); const image = useImage(require('../../assets/debug.png')); const dx = width / cols; const dy = height / rows; const C = dx / 3; const defaultMesh = new Array(cols + 1) .fill(0) .map((_c, col) => new Array(rows + 1).fill(0).map((_r, row) => { const pos = vec(row * dx, col * dy); return { pos, c1: add(pos, vec(C, 0)), c2: add(pos, vec(0, C)), }; }), ) .flat(2); const rects = new Array(rows) .fill(0) .map((_r, row) => new Array(cols).fill(0).map((_c, col) => { const l = cols + 1; const tl = row * l + col; const tr = tl + 1; const bl = (row + 1) * l + col; const br = bl + 1; return [tl, tr, br, bl]; }), ) .flat(); const noises = defaultMesh.map(() => [ createNoise2D(), createNoise2D(), createNoise2D(), ]); const meshNoise = useDerivedValue(() => { return defaultMesh.map((pt, i) => { if (isEdge(pt.pos, window)) { return pt; } const [noisePos, noiseC1, noiseC2] = noises[i]; return { pos: add( pt.pos, vec( A * noisePos(clock.value / F, 0), A * noisePos(0, clock.value / F), ), ), c1: add( pt.c1, vec(A * noiseC1(clock.value / F, 0), A * noiseC1(0, clock.value / F)), ), c2: add( pt.c1, vec(A * noiseC2(clock.value / F, 0), A * noiseC2(0, clock.value / F)), ), }; }); }, [clock]); const meshGesture = useSharedValue(defaultMesh); const gesture = useHandles(meshGesture, defaultMesh, window); const mesh = play ? meshNoise : meshGesture; return ( {rects.map((r, i) => { return ( ); })} {defaultMesh.map(({pos}, index) => { if (isEdge(pos, window) || !handles) { return null; } return ( ); })} ); }; interface RectPatchProps { r: number[]; debug?: boolean; lines?: boolean; colors: string[]; mesh: SharedValue; defaultMesh: CubicBezierHandle[]; } const RectPatch = ({ r, debug, lines, colors, mesh, defaultMesh, }: RectPatchProps) => { const patch = useRectToPatch(mesh, r); return ( <> {lines && } ); };