Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

Commit

Permalink
fix: no longer recalculate the mouse target on each mouseMove event
Browse files Browse the repository at this point in the history
  • Loading branch information
prescientmoon committed Jun 17, 2020
1 parent a740ee3 commit 3ed79d1
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 139 deletions.
22 changes: 22 additions & 0 deletions src/typescript/helpers/minBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* find the smallest element in an array based on a certain criteria
*
* @param isSmaller The compare function
* @param arr The array to search trough
* @param def Fallback in case of empty arrays
*/
export const minBy = <T>(
isSmaller: (a: T, b: T) => boolean,
arr: T[]
): T | null =>
arr.reduce((acc, curr) => {
if (acc === null) {
return curr
}

if (curr === null) {
return acc
}

return isSmaller(curr, acc) ? curr : acc
}, null as T | null)
164 changes: 25 additions & 139 deletions src/typescript/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@ import {
invert23
} from "@thi.ng/matrices"
import type { GeometryCache, NodeId, NodeGeometry } from "./types/Node"
import { Vec2Like, distSq2, Vec, dist, add2 } from "@thi.ng/vectors"
import { Vec2Like, Vec, add2 } from "@thi.ng/vectors"
import {
nodeRadius,
arcStrokeWidth,
constantInputStroke,
nodeOutputRadius,
pickDistance,
nodeBackgroundOpacity,
nodeBackgrounds
} from "./constants"
import { TAU } from "@thi.ng/math"
import { ADT } from "ts-adt"
import { Type } from "@thi.ng/geom-api"
import { closestPoint, withAttribs } from "@thi.ng/geom"
import { withAttribs } from "@thi.ng/geom"
import { isPressed, MouseButtons } from "./mouse"
import { DCons } from "@thi.ng/dcons"
import { getMouseTarget, MouseTargetKind, MouseTarget } from "./target"

// Used in the Default purescript implementation of GeomCache
export const emptyGeometryCache: GeometryCache = {
Expand All @@ -33,6 +32,7 @@ export const emptyGeometryCache: GeometryCache = {
selectedOutput: null,
selectedInput: null,
selectedNode: null,
dragging: null,
selectedNodes: new Set(),
zOrder: new DCons()
}
Expand Down Expand Up @@ -161,131 +161,6 @@ export const renderScene = (
})
}

interface IHasNode {
node: NodeGeometry
}

enum MouseTargetKind {
Nothing,
Node,
NodeInput,
NodeOutput
}

type MouseTarget = ADT<{
[MouseTargetKind.NodeInput]: IHasNode & { index: number; geom: g.Arc }
[MouseTargetKind.Node]: IHasNode & { id: NodeId }
[MouseTargetKind.NodeOutput]: IHasNode
[MouseTargetKind.Nothing]: {}
}>

/**
* find the smallest element in an array based on a certain criteria
*
* @param isSmaller The compare function
* @param arr The array to search trough
* @param def Fallback in case of empty arrays
*/
const minBy = <T>(isSmaller: (a: T, b: T) => boolean, arr: T[]): T | null =>
arr.reduce((acc, curr) => {
if (acc === null) {
return curr
}

if (curr === null) {
return acc
}

return isSmaller(curr, acc) ? curr : acc
}, null as T | null)

/**
* Finds the object in the scene the mouse is hovering over
*
* @param mousePosition The position the mouse is at
* @param cache The geometry cache to search trough
*/
const getMouseTarget = (
mousePosition: Vec,
cache: GeometryCache
): MouseTarget => {
if (cache.nodes.size === 0) {
return {
_type: MouseTargetKind.Nothing
}
}

const nodes = [...cache.nodes.values()]

const distanceToMouse = (position: Vec) => dist(mousePosition, position)

const distanceToMouseSq = (position: Vec) => distSq2(mousePosition, position)

const closestOutput = minBy((a, b) => {
return distanceToMouseSq(a.output!.pos) < distanceToMouseSq(b.output!.pos)
}, nodes)

if (
closestOutput !== null &&
distanceToMouse(closestOutput.output.pos) < pickDistance.output
) {
return {
_type: MouseTargetKind.NodeOutput,
node: closestOutput
}
}

const closestInput = minBy(
(a, b) => {
return distanceToMouse(a.closest) < distanceToMouse(b.closest)
},
nodes.flatMap((node) =>
node.inputs[0].attribs?.selectable
? node.inputs.map((input, index) => ({
index,
node,
closest: closestPoint(input, mousePosition)!
}))
: []
)
)

if (
closestInput &&
distanceToMouse(closestInput.closest) < pickDistance.input
) {
return {
_type: MouseTargetKind.NodeInput,
node: closestInput.node,
index: closestInput.index,
geom: closestInput.node.inputs[closestInput.index] as g.Arc
}
}

// Rn this is the same as the output one but in the future nodes might not have outputs.
const closestNode = minBy(
([, a], [, b]) => {
return distanceToMouseSq(a.output!.pos) < distanceToMouseSq(b.output!.pos)
},
[...cache.nodes.entries()]
)

if (
closestNode &&
distanceToMouse(closestNode[1].output!.pos) < pickDistance.node
) {
return {
_type: MouseTargetKind.Node,
node: closestNode[1],
id: closestNode[0]
}
}

return {
_type: MouseTargetKind.Nothing
}
}

/**
* Selects a particular node in a geometry.
*
Expand Down Expand Up @@ -332,14 +207,12 @@ const unselectNode = (cache: GeometryCache, node: NodeGeometry, id: NodeId) => {
export const onMouseUp = (ctx: CanvasRenderingContext2D) => (
event: MouseEvent
) => (cache: GeometryCache) => () => {
const mouse = [event.pageX, event.pageY]
const transform = getMouseTransform(ctx, cache)
const mousePosition = mulV23(null, transform, mouse)

for (const [id, node] of cache.nodes) {
unselectNode(cache, node, id)
}

cache.dragging = null

renderScene(ctx, cache)
}

Expand All @@ -361,6 +234,13 @@ export const onMouseDown = (ctx: CanvasRenderingContext2D) => (
selectNode(cache, target.node, target.id)
}

if (
target._type === MouseTargetKind.Node ||
target._type === MouseTargetKind.Nothing
) {
cache.dragging = target
}

renderScene(ctx, cache)
}
/**
Expand Down Expand Up @@ -397,13 +277,19 @@ const pan = (cache: GeometryCache, offset: Vec) => {
export const onMouseMove = (ctx: CanvasRenderingContext2D) => (
event: MouseEvent
) => (cache: GeometryCache) => () => {
const mouse = [event.pageX, event.pageY]
const transform = getMouseTransform(ctx, cache)
const mousePosition = mulV23(null, transform, mouse)
const pressed = isPressed(event.buttons)
let target: MouseTarget

const target = getMouseTarget(mousePosition, cache)
const nodes = [...cache.nodes.values()]
if (cache.dragging) {
target = cache.dragging
} else {
const mouse = [event.pageX, event.pageY]
const transform = getMouseTransform(ctx, cache)
const mousePosition = mulV23(null, transform, mouse)

target = getMouseTarget(mousePosition, cache)
}

const pressed = isPressed(event.buttons)

// On hover effects for outputs
if (event.buttons === 0) {
Expand Down
112 changes: 112 additions & 0 deletions src/typescript/target.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { NodeGeometry, NodeId, GeometryCache } from "./types/Node"
import { ADT } from "ts-adt"
import * as g from "@thi.ng/geom"
import { Vec, dist, distSq2 } from "@thi.ng/vectors"
import { minBy } from "./helpers/minBy"
import { pickDistance } from "./constants"
import { closestPoint } from "@thi.ng/geom"

interface IHasNode {
node: NodeGeometry
}

export enum MouseTargetKind {
Nothing,
Node,
NodeInput,
NodeOutput
}

export type MouseTarget = ADT<{
[MouseTargetKind.NodeInput]: IHasNode & { index: number; geom: g.Arc }
[MouseTargetKind.Node]: IHasNode & { id: NodeId }
[MouseTargetKind.NodeOutput]: IHasNode
[MouseTargetKind.Nothing]: {}
}>

/**
* Finds the object in the scene the mouse is hovering over
*
* @param mousePosition The position the mouse is at
* @param cache The geometry cache to search trough
*/
export const getMouseTarget = (
mousePosition: Vec,
cache: GeometryCache
): MouseTarget => {
if (cache.nodes.size === 0) {
return {
_type: MouseTargetKind.Nothing
}
}

const nodes = [...cache.nodes.values()]

const distanceToMouse = (position: Vec) => dist(mousePosition, position)

const distanceToMouseSq = (position: Vec) => distSq2(mousePosition, position)

const closestOutput = minBy((a, b) => {
return distanceToMouseSq(a.output!.pos) < distanceToMouseSq(b.output!.pos)
}, nodes)

if (
closestOutput !== null &&
distanceToMouse(closestOutput.output.pos) < pickDistance.output
) {
return {
_type: MouseTargetKind.NodeOutput,
node: closestOutput
}
}

const closestInput = minBy(
(a, b) => {
return distanceToMouse(a.closest) < distanceToMouse(b.closest)
},
nodes.flatMap((node) =>
node.inputs[0].attribs?.selectable
? node.inputs.map((input, index) => ({
index,
node,
closest: closestPoint(input, mousePosition)!
}))
: []
)
)

if (
closestInput &&
distanceToMouse(closestInput.closest) < pickDistance.input
) {
return {
_type: MouseTargetKind.NodeInput,
node: closestInput.node,
index: closestInput.index,
geom: closestInput.node.inputs[closestInput.index] as g.Arc
}
}

// Rn this is the same as the output one but in the future nodes might not have outputs.
const closestNode = minBy(
([, a], [, b]) => {
return distanceToMouseSq(a.output!.pos) < distanceToMouseSq(b.output!.pos)
},
[...cache.nodes.entries()]
)

if (
closestNode &&
distanceToMouse(closestNode[1].output!.pos) < pickDistance.node
) {
return {
_type: MouseTargetKind.Node,
node: closestNode[1],
id: closestNode[0]
}
}

return {
_type: MouseTargetKind.Nothing
}
}
2 changes: 2 additions & 0 deletions src/typescript/types/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Mat23Like } from "@thi.ng/matrices"
import { Circle, Arc } from "@thi.ng/geom"
import { Vec } from "@thi.ng/vectors"
import { DCons } from "@thi.ng/dcons"
import { MouseTarget } from "../target"

/**
* Data we need to get to be able to update the way a node looks.
Expand Down Expand Up @@ -47,4 +48,5 @@ export type GeometryCache = {
selectedInput: Arc | null
selectedNodes: Set<NodeId>
zOrder: DCons<NodeId>
dragging: null | MouseTarget
}

0 comments on commit 3ed79d1

Please sign in to comment.