inky boxes

This commit is contained in:
Steve Ruiz 2021-06-07 22:12:14 +01:00
parent 350c1debde
commit 84c93060d0
19 changed files with 304 additions and 31 deletions

View file

@ -11,10 +11,6 @@ import Bounds from './bounds/bounding-box'
import BoundsBg from './bounds/bounds-bg'
import Selected from './selected'
import Handles from './bounds/handles'
import { isMobile, screenToWorld, throttle } from 'utils/utils'
import session from 'state/session'
import { PointerInfo } from 'types'
import { fastDrawUpdate } from 'state/hacks'
import useCanvasEvents from 'hooks/useCanvasEvents'
export default function Canvas() {
@ -56,16 +52,9 @@ const MainSVG = styled('svg', {
zIndex: 100,
backgroundColor: '$canvas',
pointerEvents: 'all',
// cursor: 'none',
'& *': {
userSelect: 'none',
},
})
// const throttledPointerMove = throttle((payload: any) => {
// state.send('MOVED_POINTER', payload)
// }, 16)
const throttledPointerMove = (payload: any) => {
state.send('MOVED_POINTER', payload)
}

View file

@ -0,0 +1,64 @@
import React, { useEffect, useRef } from 'react'
import styled from 'styles'
export default function Cursor() {
const rCursor = useRef<SVGSVGElement>(null)
useEffect(() => {
function updatePosition(e: PointerEvent) {
console.log('hi')
const cursor = rCursor.current
cursor.setAttribute(
'transform',
`translate(${e.clientX - 12} ${e.clientY - 10})`
)
}
document.body.addEventListener('pointermove', updatePosition)
return () => {
document.body.removeEventListener('pointermove', updatePosition)
}
}, [])
return (
<StyledCursor
ref={rCursor}
width="35px"
height="35px"
viewBox="0 0 35 35"
version="1.1"
pointerEvents="none"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<path
d="M12,24.4219 L12,8.4069 L23.591,20.0259 L16.81,20.0259 L16.399,20.1499 L12,24.4219 Z"
id="point-border"
fill="#FFFFFF"
/>
<path
d="M21.0845,25.0962 L17.4795,26.6312 L12.7975,15.5422 L16.4835,13.9892 L21.0845,25.0962 Z"
id="stem-border"
fill="#FFFFFF"
/>
<path
d="M19.751,24.4155 L17.907,25.1895 L14.807,17.8155 L16.648,17.0405 L19.751,24.4155 Z"
id="stem"
fill="#000000"
/>
<path
d="M13,10.814 L13,22.002 L15.969,19.136 L16.397,18.997 L21.165,18.997 L13,10.814 Z"
id="point"
fill="#000000"
/>
</StyledCursor>
)
}
const StyledCursor = styled('g', {
position: 'absolute',
zIndex: 1000,
top: 0,
left: 0,
})

View file

@ -1,23 +1,9 @@
import { v4 as uuid } from 'uuid'
import * as vec from 'utils/vec'
import * as svg from 'utils/svg'
import {
ArrowShape,
Bounds,
ColorStyle,
DashStyle,
ShapeHandle,
ShapeType,
SizeStyle,
} from 'types'
import { ArrowShape, Bounds, ShapeHandle, ShapeType } from 'types'
import { registerShapeUtils } from './index'
import {
circleFromThreePoints,
clamp,
getBoundsCenter,
isAngleBetween,
rotateBounds,
} from 'utils/utils'
import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
import { pointInBounds } from 'utils/bounds'
import {
intersectArcBounds,
@ -72,6 +58,7 @@ const arrow = registerShapeUtils<ArrowShape>({
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Arrow,
isGenerated: false,
name: 'Arrow',

View file

@ -14,6 +14,7 @@ const circle = registerShapeUtils<CircleShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Circle,
isGenerated: false,
name: 'Circle',

View file

@ -13,6 +13,7 @@ const dot = registerShapeUtils<DotShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Dot,
isGenerated: false,
name: 'Dot',

View file

@ -24,6 +24,7 @@ const draw = registerShapeUtils<DrawShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Draw,
isGenerated: false,
name: 'Draw',

View file

@ -19,6 +19,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Ellipse,
isGenerated: false,
name: 'Ellipse',

View file

@ -26,6 +26,7 @@ const group = registerShapeUtils<GroupShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Group,
isGenerated: false,
name: 'Group',

View file

@ -15,6 +15,7 @@ const line = registerShapeUtils<LineShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Line,
isGenerated: false,
name: 'Line',

View file

@ -13,6 +13,7 @@ const polyline = registerShapeUtils<PolylineShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Polyline,
isGenerated: false,
name: 'Polyline',

View file

@ -14,6 +14,7 @@ const ray = registerShapeUtils<RayShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Ray,
isGenerated: false,
name: 'Ray',

View file

@ -2,8 +2,11 @@ import { v4 as uuid } from 'uuid'
import * as vec from 'utils/vec'
import { RectangleShape, ShapeType } from 'types'
import { registerShapeUtils } from './index'
import { translateBounds } from 'utils/utils'
import { getSvgPathFromStroke, translateBounds, getNoise } from 'utils/utils'
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
import getStroke from 'perfect-freehand'
const pathCache = new WeakMap<RectangleShape, string>([])
const rectangle = registerShapeUtils<RectangleShape>({
boundsCache: new WeakMap([]),
@ -11,6 +14,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
create(props) {
return {
id: uuid(),
seed: Math.random(),
type: ShapeType.Rectangle,
isGenerated: false,
name: 'Rectangle',
@ -28,19 +32,28 @@ const rectangle = registerShapeUtils<RectangleShape>({
}
},
render({ id, size, radius, style }) {
render(shape) {
const { id, size, radius, style, point } = shape
const styles = getShapeStyle(style)
if (!pathCache.has(shape)) {
renderPath(shape)
}
const path = pathCache.get(shape)
return (
<g id={id}>
<rect
id={id}
rx={radius}
ry={radius}
x={+styles.strokeWidth / 2}
y={+styles.strokeWidth / 2}
width={Math.max(0, size[0] + -styles.strokeWidth)}
height={Math.max(0, size[1] + -styles.strokeWidth)}
strokeWidth={0}
/>
<path d={path} fill={styles.stroke} />
</g>
)
},
@ -103,3 +116,69 @@ const rectangle = registerShapeUtils<RectangleShape>({
})
export default rectangle
function easeInOut(t: number) {
return t * (2 - t)
}
function ease(t: number) {
return t * t * t
}
function pointsBetween(a: number[], b: number[], steps = 6) {
return Array.from(Array(steps))
.map((_, i) => ease(i / steps))
.map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
}
function renderPath(shape: RectangleShape) {
const styles = getShapeStyle(shape.style)
const noise = getNoise(shape.seed)
const off = -0.25 + shape.seed / 2
const offsets = Array.from(Array(4)).map((_, i) => [
noise(i, i + 1) * off * 16,
noise(i + 2, i + 3) * off * 16,
])
const [w, h] = shape.size
const tl = vec.add([0, 0], offsets[0])
const tr = vec.add([w, 0], offsets[1])
const br = vec.add([w, h], offsets[2])
const bl = vec.add([0, h], offsets[3])
const lines = shuffleArr(
[
pointsBetween(tr, br),
pointsBetween(br, bl),
pointsBetween(bl, tl),
pointsBetween(tl, tr),
],
shape.id.charCodeAt(5)
)
const stroke = getStroke(
[
...lines.flat().slice(4),
...lines[0].slice(0, 4),
lines[0][4],
lines[0][5],
lines[0][5],
],
{
size: 1 + +styles.strokeWidth * 2,
thinning: 0.6,
easing: (t) => t * t * t * t,
end: { taper: +styles.strokeWidth * 20 },
start: { taper: +styles.strokeWidth * 20 },
simulatePressure: false,
}
)
pathCache.set(shape, getSvgPathFromStroke(stroke))
}
function shuffleArr<T>(arr: T[], offset: number): T[] {
return arr.map((_, i) => arr[(i + offset) % arr.length])
}

16
public/icons/grab.svg Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="35px" height="35px" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="openhand">
<g id="bg-copy" fill="#FFFFFF" opacity="1">
<rect id="bg" x="0" y="0" width="35" height="35"></rect>
</g>
<path d="M13.5557,17.5742 C13.4577,17.1992 13.3597,16.7272 13.1497,16.0222 C12.9827,15.4652 12.8077,15.1632 12.6797,14.7892 C12.5247,14.3342 12.3767,14.0682 12.1837,13.6082 C12.0447,13.2792 11.8197,12.5602 11.7267,12.1682 C11.6077,11.6592 11.7597,11.2442 11.9707,10.9622 C12.2237,10.6232 12.9327,10.4722 13.3277,10.6112 C13.6987,10.7412 14.0717,11.1232 14.2437,11.3992 C14.5317,11.8592 14.6007,12.0312 14.9607,12.9412 C15.3537,13.9332 15.5247,14.8592 15.5717,15.1722 L15.6567,15.6242 C15.6557,15.5842 15.6137,14.5022 15.6127,14.4622 C15.5777,13.4332 15.5527,12.6392 15.5747,11.5232 C15.5767,11.3972 15.6387,10.9362 15.6587,10.8082 C15.7367,10.3082 15.9637,10.0082 16.3317,9.8292 C16.7437,9.6282 17.2577,9.6142 17.7327,9.8122 C18.1557,9.9852 18.3587,10.3622 18.4197,10.8342 C18.4337,10.9432 18.5137,11.8212 18.5127,11.9412 C18.4997,12.9662 18.5187,13.5822 18.5277,14.1152 C18.5317,14.3462 18.5307,15.7402 18.5447,15.5842 C18.6057,14.9282 18.6387,12.3952 18.8887,11.6422 C19.0327,11.2092 19.2937,10.8962 19.6827,10.7132 C20.1137,10.5102 20.7957,10.6432 21.0867,10.9562 C21.3717,11.2612 21.5327,11.6482 21.5687,12.1092 C21.6007,12.5142 21.5497,13.0062 21.5487,13.3542 C21.5487,14.2212 21.5277,14.6782 21.5117,15.4752 C21.5107,15.5132 21.4967,15.7732 21.5347,15.6572 C21.6287,15.3772 21.7227,15.1152 21.8007,14.9122 C21.8497,14.7872 22.0417,14.2982 22.1597,14.0532 C22.2737,13.8192 22.3707,13.6842 22.5747,13.3652 C22.7747,13.0522 22.9897,12.9172 23.2427,12.8042 C23.7827,12.5692 24.3517,12.9162 24.5437,13.3952 C24.6297,13.6102 24.5527,14.1082 24.5157,14.5002 C24.4547,15.1472 24.2617,15.8062 24.1637,16.1482 C24.0357,16.5952 23.8897,17.3832 23.8237,17.7492 C23.7517,18.1432 23.5897,19.1312 23.4647,19.5692 C23.3787,19.8702 23.0937,20.5472 22.8127,20.9532 C22.8127,20.9532 21.7387,22.2032 21.6207,22.7652 C21.5037,23.3282 21.5427,23.3322 21.5197,23.7302 C21.4957,24.1292 21.6407,24.6532 21.6407,24.6532 C21.6407,24.6532 20.8387,24.7572 20.4067,24.6872 C20.0157,24.6252 19.5317,23.8462 19.4067,23.6092 C19.2347,23.2812 18.8677,23.3442 18.7247,23.5862 C18.4997,23.9692 18.0157,24.6562 17.6737,24.6992 C17.0057,24.7832 15.6197,24.7292 14.5347,24.7192 C14.5347,24.7192 14.7197,23.7082 14.3077,23.3612 C14.0027,23.1012 13.4777,22.5772 13.1637,22.3012 L12.3317,21.3802 C12.0477,21.0202 11.7027,20.2872 11.0887,19.3952 C10.7407,18.8912 10.0617,18.3102 9.8047,17.8162 C9.5817,17.3912 9.4737,16.8622 9.6147,16.4912 C9.8397,15.8972 10.2897,15.5942 10.9767,15.6592 C11.4957,15.7092 11.8247,15.8652 12.2147,16.1962 C12.4397,16.3862 12.7877,16.7302 12.9647,16.9442 C13.1277,17.1392 13.1677,17.2202 13.3417,17.4532 C13.5717,17.7602 13.6437,17.9122 13.5557,17.5742" id="hand" fill="#FFFFFF"></path>
<path d="M13.5557,17.5742 C13.4577,17.1992 13.3597,16.7272 13.1497,16.0222 C12.9827,15.4652 12.8077,15.1632 12.6797,14.7892 C12.5247,14.3342 12.3767,14.0682 12.1837,13.6082 C12.0447,13.2792 11.8197,12.5602 11.7267,12.1682 C11.6077,11.6592 11.7597,11.2442 11.9707,10.9622 C12.2237,10.6232 12.9327,10.4722 13.3277,10.6112 C13.6987,10.7412 14.0717,11.1232 14.2437,11.3992 C14.5317,11.8592 14.6007,12.0312 14.9607,12.9412 C15.3537,13.9332 15.5247,14.8592 15.5717,15.1722 L15.6567,15.6242 C15.6557,15.5842 15.6137,14.5022 15.6127,14.4622 C15.5777,13.4332 15.5527,12.6392 15.5747,11.5232 C15.5767,11.3972 15.6387,10.9362 15.6587,10.8082 C15.7367,10.3082 15.9637,10.0082 16.3317,9.8292 C16.7437,9.6282 17.2577,9.6142 17.7327,9.8122 C18.1557,9.9852 18.3587,10.3622 18.4197,10.8342 C18.4337,10.9432 18.5137,11.8212 18.5127,11.9412 C18.4997,12.9662 18.5187,13.5822 18.5277,14.1152 C18.5317,14.3462 18.5307,15.7402 18.5447,15.5842 C18.6057,14.9282 18.6387,12.3952 18.8887,11.6422 C19.0327,11.2092 19.2937,10.8962 19.6827,10.7132 C20.1137,10.5102 20.7957,10.6432 21.0867,10.9562 C21.3717,11.2612 21.5327,11.6482 21.5687,12.1092 C21.6007,12.5142 21.5497,13.0062 21.5487,13.3542 C21.5487,14.2212 21.5277,14.6782 21.5117,15.4752 C21.5107,15.5132 21.4967,15.7732 21.5347,15.6572 C21.6287,15.3772 21.7227,15.1152 21.8007,14.9122 C21.8497,14.7872 22.0417,14.2982 22.1597,14.0532 C22.2737,13.8192 22.3707,13.6842 22.5747,13.3652 C22.7747,13.0522 22.9897,12.9172 23.2427,12.8042 C23.7827,12.5692 24.3517,12.9162 24.5437,13.3952 C24.6297,13.6102 24.5527,14.1082 24.5157,14.5002 C24.4547,15.1472 24.2617,15.8062 24.1637,16.1482 C24.0357,16.5952 23.8897,17.3832 23.8237,17.7492 C23.7517,18.1432 23.5897,19.1312 23.4647,19.5692 C23.3787,19.8702 23.0937,20.5472 22.8127,20.9532 C22.8127,20.9532 21.7387,22.2032 21.6207,22.7652 C21.5037,23.3282 21.5427,23.3322 21.5197,23.7302 C21.4957,24.1292 21.6407,24.6532 21.6407,24.6532 C21.6407,24.6532 20.8387,24.7572 20.4067,24.6872 C20.0157,24.6252 19.5317,23.8462 19.4067,23.6092 C19.2347,23.2812 18.8677,23.3442 18.7247,23.5862 C18.4997,23.9692 18.0157,24.6562 17.6737,24.6992 C17.0057,24.7832 15.6197,24.7292 14.5347,24.7192 C14.5347,24.7192 14.7197,23.7082 14.3077,23.3612 C14.0027,23.1012 13.4777,22.5772 13.1637,22.3012 L12.3317,21.3802 C12.0477,21.0202 11.7027,20.2872 11.0887,19.3952 C10.7407,18.8912 10.0617,18.3102 9.8047,17.8162 C9.5817,17.3912 9.4737,16.8622 9.6147,16.4912 C9.8397,15.8972 10.2897,15.5942 10.9767,15.6592 C11.4957,15.7092 11.8247,15.8652 12.2147,16.1962 C12.4397,16.3862 12.7877,16.7302 12.9647,16.9442 C13.1277,17.1392 13.1677,17.2202 13.3417,17.4532 C13.5717,17.7602 13.6437,17.9122 13.5557,17.5742" id="hand-border" stroke="#000000" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M20.5664,21.7344 L20.5664,18.2754" id="line3" stroke="#000000" stroke-width="0.75" stroke-linecap="round"></path>
<path d="M18.5508,21.7461 L18.5348,18.2731" id="line2" stroke="#000000" stroke-width="0.75" stroke-linecap="round"></path>
<path d="M16.5547,18.3047 L16.5757,21.7307" id="line1" stroke="#000000" stroke-width="0.75" stroke-linecap="round"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

15
public/icons/pointer.svg Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="35px" height="35px" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="pointer">
<g id="bg" fill="#FFFFFF" opacity="0.00999999978">
<rect x="0" y="0" width="35" height="35"></rect>
</g>
<path d="M12,24.4219 L12,8.4069 L23.591,20.0259 L16.81,20.0259 L16.399,20.1499 L12,24.4219 Z" id="point-border" fill="#FFFFFF"></path>
<path d="M21.0845,25.0962 L17.4795,26.6312 L12.7975,15.5422 L16.4835,13.9892 L21.0845,25.0962 Z" id="stem-border" fill="#FFFFFF"></path>
<path d="M19.751,24.4155 L17.907,25.1895 L14.807,17.8155 L16.648,17.0405 L19.751,24.4155 Z" id="stem" fill="#000000"></path>
<path d="M13,10.814 L13,22.002 L15.969,19.136 L16.397,18.997 L21.165,18.997 L13,10.814 Z" id="point" fill="#000000"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

13
public/icons/resize.svg Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="35px" height="35px" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="resizenortheastsouthwest">
<g id="bg-copy" fill="#FFFFFF" opacity="1">
<rect id="bg" x="0" y="0" width="35" height="35"></rect>
</g>
<path d="M19.7432,17.0869 L15.6712,21.1549 L18.5002,23.9829 L10.0272,23.9699 L10.0142,15.4999 L12.8552,18.3419 L16.9302,14.2739 L18.3442,12.8589 L15.5002,10.0169 L23.9862,10.0169 L23.9862,18.5009 L21.1562,15.6739 L19.7432,17.0869 Z" id="resize-border" fill="#FFFFFF"></path>
<path d="M18.6826,16.7334 L14.2556,21.1574 L16.0836,22.9854 L11.0276,22.9694 L11.0136,17.9154 L12.8556,19.7564 L17.2836,15.3344 L19.7576,12.8594 L17.9136,11.0164 L22.9866,11.0164 L22.9866,16.0874 L21.1566,14.2594 L18.6826,16.7334 Z" id="resize" fill="#000000"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

17
state/cursor.ts Normal file
View file

@ -0,0 +1,17 @@
const cursorSvgs = {
default: 'pointer',
resize: 'resize',
grab: 'grab',
}
class Cursor {
setCursor(cursor: keyof typeof cursorSvgs, rotation = 0) {
document.body.style.setProperty('cursor', `url(${cursorSvgs[cursor]}.svg)`)
}
resetCursor() {
this.setCursor('default')
}
}
export default new Cursor()

View file

@ -189,6 +189,7 @@ export function getTranslateSnapshot(data: Data) {
const clone = {
...shape,
id: uuid(),
seed: Math.random(),
parentId: shape.parentId,
childIndex: getChildIndexAbove(cData, shape.id),
}

View file

@ -105,6 +105,7 @@ export type ShapeStyles = {
export interface BaseShape {
id: string
seed: number
type: ShapeType
parentId: string
childIndex: number

View file

@ -1677,3 +1677,86 @@ export function setSelectedIds(data: Data, ids: string[]) {
export function setToArray<T>(set: Set<T>): T[] {
return Array.from(set.values())
}
const G2 = (3.0 - Math.sqrt(3.0)) / 6.0
const Grad = [
[1, 1],
[-1, 1],
[1, -1],
[-1, -1],
[1, 0],
[-1, 0],
[1, 0],
[-1, 0],
[0, 1],
[0, -1],
[0, 1],
[0, -1],
]
// Thanks to joshforisha
// https://github.com/joshforisha/fast-simplex-noise-js/blob/main/src/2d.ts
export function getNoise(seed = Math.random()) {
const p = new Uint8Array(256)
for (let i = 0; i < 256; i++) p[i] = i
let n: number
let q: number
for (let i = 255; i > 0; i--) {
n = Math.floor((i + 1) * seed)
q = p[i]
p[i] = p[n]
p[n] = q
}
const perm = new Uint8Array(512)
const permMod12 = new Uint8Array(512)
for (let i = 0; i < 512; i++) {
perm[i] = p[i & 255]
permMod12[i] = perm[i] % 12
}
return (x: number, y: number): number => {
// Skew the input space to determine which simplex cell we're in
const s = (x + y) * 0.5 * (Math.sqrt(3.0) - 1.0) // Hairy factor for 2D
const i = Math.floor(x + s)
const j = Math.floor(y + s)
const t = (i + j) * G2
const X0 = i - t // Unskew the cell origin back to (x,y) space
const Y0 = j - t
const x0 = x - X0 // The x,y distances from the cell origin
const y0 = y - Y0
// Determine which simplex we are in.
const i1 = x0 > y0 ? 1 : 0
const j1 = x0 > y0 ? 0 : 1
// Offsets for corners
const x1 = x0 - i1 + G2
const y1 = y0 - j1 + G2
const x2 = x0 - 1.0 + 2.0 * G2
const y2 = y0 - 1.0 + 2.0 * G2
// Work out the hashed gradient indices of the three simplex corners
const ii = i & 255
const jj = j & 255
const g0 = Grad[permMod12[ii + perm[jj]]]
const g1 = Grad[permMod12[ii + i1 + perm[jj + j1]]]
const g2 = Grad[permMod12[ii + 1 + perm[jj + 1]]]
// Calculate the contribution from the three corners
const t0 = 0.5 - x0 * x0 - y0 * y0
const n0 = t0 < 0 ? 0.0 : Math.pow(t0, 4) * (g0[0] * x0 + g0[1] * y0)
const t1 = 0.5 - x1 * x1 - y1 * y1
const n1 = t1 < 0 ? 0.0 : Math.pow(t1, 4) * (g1[0] * x1 + g1[1] * y1)
const t2 = 0.5 - x2 * x2 - y2 * y2
const n2 = t2 < 0 ? 0.0 : Math.pow(t2, 4) * (g2[0] * x2 + g2[1] * y2)
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1, 1]
return 70.14805770653952 * (n0 + n1 + n2)
}
}