|
|
|
@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'preact/hooks'
|
|
|
|
|
import { enforceDistance } from '../../lib/math/constraint.js'
|
|
|
|
|
import { resampleCurve } from '../../lib/math/curves.js'
|
|
|
|
|
import { Vec2, Vec3 } from '../../lib/math/math.js'
|
|
|
|
|
import { MODE_DRAW, MODE_FLIP, MODE_DRAG } from '../pages.jsx'
|
|
|
|
|
|
|
|
|
|
function mod(i, modulus) {
|
|
|
|
|
const r = i % modulus
|
|
|
|
@ -58,22 +59,26 @@ class KnotSimulation {
|
|
|
|
|
this.mousePosition = [x, y]
|
|
|
|
|
|
|
|
|
|
if (buttons === 1) {
|
|
|
|
|
this.ghostPath = []
|
|
|
|
|
}
|
|
|
|
|
if (buttons === 2) {
|
|
|
|
|
const [i] = Vec2.findNearest(
|
|
|
|
|
this.particleSimulation.positions.map(([x, y]) => [x, y]),
|
|
|
|
|
this.mousePosition
|
|
|
|
|
)
|
|
|
|
|
this.draggingIndex = i
|
|
|
|
|
}
|
|
|
|
|
if (buttons === 4) {
|
|
|
|
|
const { positions } = this.particleSimulation
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < positions.length; i++) {
|
|
|
|
|
if (Vec2.distance2([x, y], positions[i]) < INVERT_CROSSING_RADIUS) {
|
|
|
|
|
positions[i][2] *= -1
|
|
|
|
|
}
|
|
|
|
|
switch (this.modeRef.current) {
|
|
|
|
|
case MODE_DRAW:
|
|
|
|
|
this.ghostPath = []
|
|
|
|
|
break;
|
|
|
|
|
case MODE_FLIP:
|
|
|
|
|
const { positions } = this.particleSimulation
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < positions.length; i++) {
|
|
|
|
|
if (Vec2.distance2([x, y], positions[i]) < INVERT_CROSSING_RADIUS) {
|
|
|
|
|
positions[i][2] *= -1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MODE_DRAG:
|
|
|
|
|
const [i] = Vec2.findNearest(
|
|
|
|
|
this.particleSimulation.positions.map(([x, y]) => [x, y]),
|
|
|
|
|
this.mousePosition
|
|
|
|
|
)
|
|
|
|
|
this.draggingIndex = i
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -318,9 +323,9 @@ class KnotSimulation {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const KnotLayer = ({ knotRef }) => {
|
|
|
|
|
export const KnotLayer = ({ knotRef, modeRef }) => {
|
|
|
|
|
const canvasRef = useRef(null)
|
|
|
|
|
const [knotSim] = useState(() => new KnotSimulation(knotRef))
|
|
|
|
|
const [knotSim] = useState(() => new KnotSimulation(knotRef, modeRef))
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
@ -353,17 +358,17 @@ export const KnotLayer = ({ knotRef }) => {
|
|
|
|
|
return (
|
|
|
|
|
<canvas
|
|
|
|
|
ref={canvasRef}
|
|
|
|
|
onMouseDown={e => {
|
|
|
|
|
onPointerDown={e => {
|
|
|
|
|
knotSim.onMouseDown(e.offsetX, e.offsetY, e.buttons)
|
|
|
|
|
}}
|
|
|
|
|
onMouseMove={e => {
|
|
|
|
|
onPointerMove={e => {
|
|
|
|
|
if (canvasRef.current && e.buttons > 0) {
|
|
|
|
|
knotSim.onMouseDrag(e.offsetX, e.offsetY, e.buttons)
|
|
|
|
|
requestAnimationFrame(() => knotSim.render(canvasRef.current.graphicsContext))
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onContextMenu={e => e.preventDefault()}
|
|
|
|
|
onMouseUp={e => {
|
|
|
|
|
onPointerUp={e => {
|
|
|
|
|
if (canvasRef.current) {
|
|
|
|
|
knotSim.onMouseUp()
|
|
|
|
|
requestAnimationFrame(() => knotSim.render(canvasRef.current.graphicsContext))
|
|
|
|
|