From 7427a0284272b88d8107759c3308b7bcc9cc03e4 Mon Sep 17 00:00:00 2001 From: Matei Adriel Date: Wed, 17 Jul 2019 14:17:15 +0300 Subject: [PATCH] Basic pin rendering --- .../canvas}/helpers/clearCanvas.ts | 2 +- .../canvas}/helpers/drawPolygon.ts | 2 +- .../canvas/helpers/drawRotatedSquare.ts | 33 ++++++++++ .../canvas/helpers/drawRoundedSquare.ts | 23 +++++++ src/common/canvas/helpers/useTransform.ts | 14 ++++ .../math}/classes/Transform.ts | 32 ++++----- src/common/math/helpers/pointInCircle.ts | 10 +++ .../math}/helpers/pointInSquare.ts | 3 +- src/common/math/types/vector2.ts | 1 + src/common/math/types/vector3.ts | 1 + src/common/math/types/vector4.ts | 1 + src/common/math/types/vector8.ts | 10 +++ src/modules/core/components/App.scss | 4 ++ src/modules/core/components/Canvas.tsx | 18 +++-- src/modules/core/components/FluidCanvas.tsx | 2 +- src/modules/simulation/classes/Gate.ts | 59 ++++++++++++++++- src/modules/simulation/classes/Pin.ts | 8 +++ src/modules/simulation/constants.ts | 22 +++++++ src/modules/simulation/types/GateTemplate.ts | 23 +++++++ .../simulationRenderer/classes/Camera.ts | 3 +- .../classes/SimulationRenderer.ts | 56 +++++++++++++--- src/modules/simulationRenderer/constants.ts | 12 ++-- .../helpers/drawRotatedSquare.ts | 17 ----- .../simulationRenderer/helpers/pinFill.ts | 15 +++++ .../simulationRenderer/helpers/pinPosition.ts | 66 +++++++++++++++++++ .../helpers/projectPoint.ts | 3 +- .../helpers/renderClickedPins.ts | 29 ++++++++ .../simulationRenderer/helpers/renderGate.ts | 18 +++-- .../helpers/renderGateShadow.ts | 21 ------ .../simulationRenderer/helpers/renderPins.ts | 53 +++++++++++++++ .../helpers/renderSimulation.ts | 25 ++----- .../simulationRenderer/types/DeepPartial.ts | 7 ++ .../simulationRenderer/types/SelectedPins.ts | 12 ++++ .../types/SimulationRendererOptions.ts | 12 ++-- src/modules/vector2/helpers/basic.ts | 2 +- src/modules/vector2/helpers/minmaxVector.ts | 2 +- src/modules/vector2/helpers/rotate.ts | 2 +- src/modules/vector2/helpers/smoothStep.ts | 2 +- 38 files changed, 510 insertions(+), 115 deletions(-) rename src/{modules/simulationRenderer => common/canvas}/helpers/clearCanvas.ts (62%) rename src/{modules/simulationRenderer => common/canvas}/helpers/drawPolygon.ts (84%) create mode 100644 src/common/canvas/helpers/drawRotatedSquare.ts create mode 100644 src/common/canvas/helpers/drawRoundedSquare.ts create mode 100644 src/common/canvas/helpers/useTransform.ts rename src/{modules/simulation => common/math}/classes/Transform.ts (93%) create mode 100644 src/common/math/helpers/pointInCircle.ts rename src/{modules/simulationRenderer => common/math}/helpers/pointInSquare.ts (70%) create mode 100644 src/common/math/types/vector2.ts create mode 100644 src/common/math/types/vector3.ts create mode 100644 src/common/math/types/vector4.ts create mode 100644 src/common/math/types/vector8.ts create mode 100644 src/modules/simulation/constants.ts create mode 100644 src/modules/simulation/types/GateTemplate.ts delete mode 100644 src/modules/simulationRenderer/helpers/drawRotatedSquare.ts create mode 100644 src/modules/simulationRenderer/helpers/pinFill.ts create mode 100644 src/modules/simulationRenderer/helpers/pinPosition.ts create mode 100644 src/modules/simulationRenderer/helpers/renderClickedPins.ts delete mode 100644 src/modules/simulationRenderer/helpers/renderGateShadow.ts create mode 100644 src/modules/simulationRenderer/helpers/renderPins.ts create mode 100644 src/modules/simulationRenderer/types/DeepPartial.ts create mode 100644 src/modules/simulationRenderer/types/SelectedPins.ts diff --git a/src/modules/simulationRenderer/helpers/clearCanvas.ts b/src/common/canvas/helpers/clearCanvas.ts similarity index 62% rename from src/modules/simulationRenderer/helpers/clearCanvas.ts rename to src/common/canvas/helpers/clearCanvas.ts index 30ec5f1..b707e06 100644 --- a/src/modules/simulationRenderer/helpers/clearCanvas.ts +++ b/src/common/canvas/helpers/clearCanvas.ts @@ -1,4 +1,4 @@ -import { SimulationRenderer } from '../classes/SimulationRenderer' +import { SimulationRenderer } from '../../../modules/simulationRenderer/classes/SimulationRenderer' export const clearCanvas = ( ctx: CanvasRenderingContext2D, diff --git a/src/modules/simulationRenderer/helpers/drawPolygon.ts b/src/common/canvas/helpers/drawPolygon.ts similarity index 84% rename from src/modules/simulationRenderer/helpers/drawPolygon.ts rename to src/common/canvas/helpers/drawPolygon.ts index c427cea..a5ab378 100644 --- a/src/modules/simulationRenderer/helpers/drawPolygon.ts +++ b/src/common/canvas/helpers/drawPolygon.ts @@ -1,4 +1,4 @@ -import { vector2 } from '../../simulation/classes/Transform' +import { vector2 } from '../../math/types/vector2' export const drawPolygon = ( ctx: CanvasRenderingContext2D, diff --git a/src/common/canvas/helpers/drawRotatedSquare.ts b/src/common/canvas/helpers/drawRotatedSquare.ts new file mode 100644 index 0000000..f5e4ebf --- /dev/null +++ b/src/common/canvas/helpers/drawRotatedSquare.ts @@ -0,0 +1,33 @@ +import { Transform } from '../../math/classes/Transform' +import { Material, Shape } from '../../../modules/simulation/types/GateTemplate' +import { roundRect } from './drawRoundedSquare' + +export const drawRotatedSquare = ( + ctx: CanvasRenderingContext2D, + { position, scale, rotation }: Transform, + shape: Shape +) => { + ctx.save() + + ctx.translate(...position) + ctx.translate(scale[0] / 2, scale[1] / 2) + + ctx.rotate(rotation) + + if (shape.rounded) { + roundRect( + ctx, + -scale[0] / 2, + -scale[1] / 2, + scale[0], + scale[1], + shape.radius + ) + + ctx.fill() + } else { + ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale) + } + + ctx.restore() +} diff --git a/src/common/canvas/helpers/drawRoundedSquare.ts b/src/common/canvas/helpers/drawRoundedSquare.ts new file mode 100644 index 0000000..70f2c00 --- /dev/null +++ b/src/common/canvas/helpers/drawRoundedSquare.ts @@ -0,0 +1,23 @@ +/** + * Credit: https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas + */ +export function roundRect( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number = 5 +) { + ctx.beginPath() + ctx.moveTo(x + radius, y) + ctx.lineTo(x + width - radius, y) + ctx.quadraticCurveTo(x + width, y, x + width, y + radius) + ctx.lineTo(x + width, y + height - radius) + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) + ctx.lineTo(x + radius, y + height) + ctx.quadraticCurveTo(x, y + height, x, y + height - radius) + ctx.lineTo(x, y + radius) + ctx.quadraticCurveTo(x, y, x + radius, y) + ctx.closePath() +} diff --git a/src/common/canvas/helpers/useTransform.ts b/src/common/canvas/helpers/useTransform.ts new file mode 100644 index 0000000..485be0d --- /dev/null +++ b/src/common/canvas/helpers/useTransform.ts @@ -0,0 +1,14 @@ +import { Transform } from '../../math/classes/Transform' +import { multiply } from '../../../modules/vector2/helpers/basic' + +export const useTransform = ( + ctx: CanvasRenderingContext2D, + { position, rotation, scale }: Transform +) => { + ctx.translate(...position) + ctx.translate(scale[0] / 2, scale[1] / 2) + + ctx.rotate(rotation) + + return new Transform(multiply(scale, -0.5), scale, 0) +} diff --git a/src/modules/simulation/classes/Transform.ts b/src/common/math/classes/Transform.ts similarity index 93% rename from src/modules/simulation/classes/Transform.ts rename to src/common/math/classes/Transform.ts index 0ae15cb..cf2dcba 100644 --- a/src/modules/simulation/classes/Transform.ts +++ b/src/common/math/classes/Transform.ts @@ -1,19 +1,5 @@ -import { allCombinations } from '../helpers/allCombinations' -import { rotateAroundVector } from '../../vector2/helpers/rotate' - -export type vector2 = [number, number] -export type vector3 = [number, number, number] -export type vector4 = [number, number, number, number] -export type vector8 = [ - number, - number, - number, - number, - number, - number, - number, - number -] +import { allCombinations } from '../../../modules/simulation/helpers/allCombinations' +import { rotateAroundVector } from '../../../modules/vector2/helpers/rotate' export class Transform { public constructor( @@ -106,3 +92,17 @@ export class Transform { this.scale = [this.width, value] } } + +export type vector2 = [number, number] +export type vector3 = [number, number, number] +export type vector4 = [number, number, number, number] +export type vector8 = [ + number, + number, + number, + number, + number, + number, + number, + number +] diff --git a/src/common/math/helpers/pointInCircle.ts b/src/common/math/helpers/pointInCircle.ts new file mode 100644 index 0000000..3495643 --- /dev/null +++ b/src/common/math/helpers/pointInCircle.ts @@ -0,0 +1,10 @@ +import { vector2 } from '../types/vector2' +import { length, relativeTo } from '../../../modules/vector2/helpers/basic' + +export const pointInCircle = ( + point: vector2, + center: vector2, + radius: number +) => { + return length(relativeTo(point, center)) < radius +} diff --git a/src/modules/simulationRenderer/helpers/pointInSquare.ts b/src/common/math/helpers/pointInSquare.ts similarity index 70% rename from src/modules/simulationRenderer/helpers/pointInSquare.ts rename to src/common/math/helpers/pointInSquare.ts index f250c60..516bfe8 100644 --- a/src/modules/simulationRenderer/helpers/pointInSquare.ts +++ b/src/common/math/helpers/pointInSquare.ts @@ -1,4 +1,5 @@ -import { vector2, Transform } from '../../simulation/classes/Transform' +import { Transform } from '../classes/Transform' +import { vector2 } from '../types/vector2' export const pointInSquare = (point: vector2, square: Transform) => { return ( diff --git a/src/common/math/types/vector2.ts b/src/common/math/types/vector2.ts new file mode 100644 index 0000000..9af9e28 --- /dev/null +++ b/src/common/math/types/vector2.ts @@ -0,0 +1 @@ +export type vector2 = [number, number] diff --git a/src/common/math/types/vector3.ts b/src/common/math/types/vector3.ts new file mode 100644 index 0000000..f4efc70 --- /dev/null +++ b/src/common/math/types/vector3.ts @@ -0,0 +1 @@ +export type vector3 = [number, number, number] diff --git a/src/common/math/types/vector4.ts b/src/common/math/types/vector4.ts new file mode 100644 index 0000000..aedce11 --- /dev/null +++ b/src/common/math/types/vector4.ts @@ -0,0 +1 @@ +export type vector4 = [number, number, number, number] diff --git a/src/common/math/types/vector8.ts b/src/common/math/types/vector8.ts new file mode 100644 index 0000000..cfe24a1 --- /dev/null +++ b/src/common/math/types/vector8.ts @@ -0,0 +1,10 @@ +export type vector8 = [ + number, + number, + number, + number, + number, + number, + number, + number +] diff --git a/src/modules/core/components/App.scss b/src/modules/core/components/App.scss index 63eaa94..0dbfb1a 100644 --- a/src/modules/core/components/App.scss +++ b/src/modules/core/components/App.scss @@ -5,3 +5,7 @@ body { display: block; overflow: hidden; } + +canvas { + background-color: #222222; +} diff --git a/src/modules/core/components/Canvas.tsx b/src/modules/core/components/Canvas.tsx index bf278c1..f6fb956 100644 --- a/src/modules/core/components/Canvas.tsx +++ b/src/modules/core/components/Canvas.tsx @@ -15,14 +15,22 @@ class Canvas extends Component { public constructor(props: {}) { super(props) - const foo = new Gate('blue') - const bar = new Gate('green') + const foo = new Gate({ + material: { + value: 'blue' + } + }) + const bar = new Gate({ + material: { + value: 'green' + } + }) foo.transform.position = [100, 100] - foo.transform.scale = [70, 70] + foo.transform.scale = [100, 100] - bar.transform.position = [200, 200] - bar.transform.scale = [70, 70] + bar.transform.position = [400, 200] + bar.transform.scale = [100, 100] this.renderer.simulation.push(foo, bar) diff --git a/src/modules/core/components/FluidCanvas.tsx b/src/modules/core/components/FluidCanvas.tsx index 51576ac..cb01886 100644 --- a/src/modules/core/components/FluidCanvas.tsx +++ b/src/modules/core/components/FluidCanvas.tsx @@ -2,7 +2,7 @@ import React, { RefObject, forwardRef, MouseEvent } from 'react' import { useObservable } from 'rxjs-hooks' import { Screen } from '../classes/Screen' import { Subject } from 'rxjs' -import { vector2 } from '../../simulation/classes/Transform' +import { vector2 } from '../../../common/math/types/vector2' const screen = new Screen() diff --git a/src/modules/simulation/classes/Gate.ts b/src/modules/simulation/classes/Gate.ts index 8d930fc..3c0663d 100644 --- a/src/modules/simulation/classes/Gate.ts +++ b/src/modules/simulation/classes/Gate.ts @@ -1,9 +1,64 @@ -import { Transform } from './Transform' +import { Transform } from '../../../common/math/classes/Transform' +import { Pin } from './Pin' +import merge from 'deepmerge' +import { GateTemplate, PinCount } from '../types/GateTemplate' +import { DefaultGateTemplate } from '../constants' + +export interface GatePins { + inputs: Pin[] + outputs: Pin[] +} + +export interface PinWrapper { + total: number + index: number + value: Pin +} export class Gate { public static lastId = 0 + public transform = new Transform() public id = Gate.lastId++ + public _pins: GatePins = { + inputs: [], + outputs: [] + } - public constructor(public color = 'blue') {} + public template: GateTemplate + + public constructor(template: DeepPartial = {}) { + this.template = merge(DefaultGateTemplate, template) as GateTemplate + + this._pins.inputs = Gate.generatePins(this.template.pins.inputs, 1) + this._pins.outputs = Gate.generatePins(this.template.pins.outputs, 2) + } + + private wrapPins(pins: Pin[]) { + const result: PinWrapper[] = [] + const length = pins.length + + for (let index = 0; index < length; index++) { + result.push({ + index, + total: length, + value: pins[index] + }) + } + + return result + } + + public get pins() { + const result = [ + ...this.wrapPins(this._pins.inputs), + ...this.wrapPins(this._pins.outputs) + ] + + return result + } + + private static generatePins(options: PinCount, type: number) { + return [...Array(options.count)].fill(true).map(() => new Pin(type)) + } } diff --git a/src/modules/simulation/classes/Pin.ts b/src/modules/simulation/classes/Pin.ts index 4b48da7..e097b78 100644 --- a/src/modules/simulation/classes/Pin.ts +++ b/src/modules/simulation/classes/Pin.ts @@ -1,6 +1,12 @@ import { SubscriptionData } from '../types/SubscriptionData' import { BehaviorSubject } from 'rxjs' +/* Types: + +First bit = input +Second bit = output + +*/ export class Pin { public state = new BehaviorSubject(false) public connectedTo = new Set() @@ -8,6 +14,8 @@ export class Pin { private pairs = new Set() private subscriptions: SubscriptionData[] = [] + public constructor(public type = 0b01) {} + public addPair(pin: Pin) { this.pairs.add(pin) diff --git a/src/modules/simulation/constants.ts b/src/modules/simulation/constants.ts new file mode 100644 index 0000000..4dcf074 --- /dev/null +++ b/src/modules/simulation/constants.ts @@ -0,0 +1,22 @@ +import { GateTemplate } from './types/GateTemplate' + +export const DefaultGateTemplate: GateTemplate = { + material: { + type: 'color', + value: 'blue' + }, + pins: { + inputs: { + count: 2, + variable: false + }, + outputs: { + count: 1, + variable: false + } + }, + shape: { + radius: 10, + rounded: true + } +} diff --git a/src/modules/simulation/types/GateTemplate.ts b/src/modules/simulation/types/GateTemplate.ts new file mode 100644 index 0000000..b4132c1 --- /dev/null +++ b/src/modules/simulation/types/GateTemplate.ts @@ -0,0 +1,23 @@ +export interface PinCount { + variable: boolean + count: number +} + +export interface Material { + type: 'color' + value: string +} + +export interface Shape { + rounded: boolean + radius: number +} + +export interface GateTemplate { + material: Material + shape: Shape + pins: { + inputs: PinCount + outputs: PinCount + } +} diff --git a/src/modules/simulationRenderer/classes/Camera.ts b/src/modules/simulationRenderer/classes/Camera.ts index ada67c3..0d7af61 100644 --- a/src/modules/simulationRenderer/classes/Camera.ts +++ b/src/modules/simulationRenderer/classes/Camera.ts @@ -1,4 +1,5 @@ -import { Transform, vector2 } from '../../simulation/classes/Transform' +import { Transform } from '../../../common/math/classes/Transform' +import { vector2 } from '../../../common/math/types/vector2' import { Screen } from '../../core/classes/Screen' import { relativeTo } from '../../vector2/helpers/basic' diff --git a/src/modules/simulationRenderer/classes/SimulationRenderer.ts b/src/modules/simulationRenderer/classes/SimulationRenderer.ts index 94328c4..ca91092 100644 --- a/src/modules/simulationRenderer/classes/SimulationRenderer.ts +++ b/src/modules/simulationRenderer/classes/SimulationRenderer.ts @@ -2,14 +2,17 @@ import { Camera } from './Camera' import { Simulation } from '../../simulation/classes/Simulation' import { Subject } from 'rxjs' import { MouseEventInfo } from '../../core/components/FluidCanvas' -import { pointInSquare } from '../helpers/pointInSquare' -import { vector2 } from '../../simulation/classes/Transform' +import { pointInSquare } from '../../../common/math/helpers/pointInSquare' +import { vector2 } from '../../../common/math/types/vector2' import { MouseVelocityManager } from './MouseVelocityManager' import { Screen } from '../../core/classes/Screen' import { relativeTo, add, invert } from '../../vector2/helpers/basic' import { SimulationRendererOptions } from '../types/SimulationRendererOptions' import { defaultSimulationRendererOptions } from '../constants' import merge from 'deepmerge' +import { getPinPosition } from '../helpers/pinPosition' +import { pointInCircle } from '../../../common/math/helpers/pointInCircle' +import { SelectedPins } from '../types/SelectedPins' export class SimulationRenderer { public mouseDownOutput = new Subject() @@ -23,12 +26,18 @@ export class SimulationRenderer { private selectedGate: number | null = null private gateSelectionOffset: vector2 = [0, 0] + public lastMousePosition: vector2 = [0, 0] public movedSelection = false public options: SimulationRendererOptions public mouseManager = new MouseVelocityManager(this.mouseMoveOutput) public screen = new Screen() public camera = new Camera() + public selectedPins: SelectedPins = { + start: null, + end: null + } + public constructor( options: Partial = {}, public simulation = new Simulation() @@ -43,11 +52,13 @@ export class SimulationRenderer { const worldPosition = this.camera.toWordPostition(event.position) const gates = Array.from(this.simulation.gates) + this.lastMousePosition = worldPosition + // We need to iterate from the last to the first // because if we have 2 overlapping gates, // we want to select the one on top for (let index = gates.length - 1; index >= 0; index--) { - const { transform, id } = gates[index] + const { transform, id, pins } = gates[index] if (pointInSquare(worldPosition, transform)) { this.mouseManager.clear(worldPosition[0]) @@ -69,9 +80,38 @@ export class SimulationRenderer { return } + + for (const pin of pins) { + const position = getPinPosition(this, transform, pin) + + if ( + pointInCircle( + worldPosition, + position, + this.options.gates.pinRadius + ) + ) { + if ( + (pin.value.type & 0b10) >> 1 && + this.selectedPins.start === null + ) { + this.selectedPins.start = { + wrapper: pin, + transform + } + } else if ( + pin.value.type & 1 && + this.selectedPins.end === null + ) { + this.selectedPins.end = { + wrapper: pin, + transform + } + } + } + } } - this.gateSelectionOffset = worldPosition this.mouseState |= 2 }) @@ -110,18 +150,16 @@ export class SimulationRenderer { if ((this.mouseState >> 1) & 1) { const offset = invert( - relativeTo(this.gateSelectionOffset, worldPosition) + relativeTo(this.lastMousePosition, worldPosition) ) this.camera.transform.position = add( this.camera.transform.position, invert(offset) ) - - this.gateSelectionOffset = this.camera.toWordPostition( - event.position - ) } + + this.lastMousePosition = this.camera.toWordPostition(event.position) }) } diff --git a/src/modules/simulationRenderer/constants.ts b/src/modules/simulationRenderer/constants.ts index 25a81f7..563a4c0 100644 --- a/src/modules/simulationRenderer/constants.ts +++ b/src/modules/simulationRenderer/constants.ts @@ -1,13 +1,13 @@ import { SimulationRendererOptions } from './types/SimulationRendererOptions' export const defaultSimulationRendererOptions: SimulationRendererOptions = { - shadows: { - enabled: true, - color: 'rgba(0,0,0,0.3)', - gateHeight: 10, - lightHeight: 100 - }, dnd: { rotation: Math.PI / 12 // 7.5 degrees + }, + gates: { + connectionLength: 30, + pinRadius: 10, + pinStrokeColor: '#888888', + pinStrokeWidth: 3 } } diff --git a/src/modules/simulationRenderer/helpers/drawRotatedSquare.ts b/src/modules/simulationRenderer/helpers/drawRotatedSquare.ts deleted file mode 100644 index e5d37b8..0000000 --- a/src/modules/simulationRenderer/helpers/drawRotatedSquare.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Transform } from '../../simulation/classes/Transform' - -export const drawRotatedSquare = ( - ctx: CanvasRenderingContext2D, - { position, scale, rotation }: Transform -) => { - ctx.save() - - ctx.translate(...position) - ctx.translate(scale[0] / 2, scale[1] / 2) - - ctx.rotate(rotation) - - ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale) - - ctx.restore() -} diff --git a/src/modules/simulationRenderer/helpers/pinFill.ts b/src/modules/simulationRenderer/helpers/pinFill.ts new file mode 100644 index 0000000..0134344 --- /dev/null +++ b/src/modules/simulationRenderer/helpers/pinFill.ts @@ -0,0 +1,15 @@ +import { Pin } from '../../simulation/classes/Pin' + +export const pinFill = (pin: Pin) => { + let color = 'rgba(0,0,0,0)' + + if (pin.connectedTo.size) { + if (pin.state) { + color = 'yellow' + } else { + color = 'grey' + } + } + + return color +} diff --git a/src/modules/simulationRenderer/helpers/pinPosition.ts b/src/modules/simulationRenderer/helpers/pinPosition.ts new file mode 100644 index 0000000..9ec8663 --- /dev/null +++ b/src/modules/simulationRenderer/helpers/pinPosition.ts @@ -0,0 +1,66 @@ +import { Transform } from '../../../common/math/classes/Transform' +import { Gate, PinWrapper } from '../../simulation/classes/Gate' +import { Pin } from '../../simulation/classes/Pin' +import { SimulationRenderer } from '../classes/SimulationRenderer' +import { vector2 } from '../../../common/math/types/vector2' +import { rotateAroundVector } from '../../vector2/helpers/rotate' + +export const calculatePinY = ( + transform: Transform, + index: number, + total: number +) => { + const space = transform.height / total + + return (space * (2 * index + 1)) / 2 +} + +export const calculatePinStart = ( + transform: Transform, + type: number, + width: number +) => { + const direction = (type >> 1) & 1 + const start = + transform.x + direction * transform.width - Number(!direction) * width + + return start +} + +export const calculatePinx = ( + start: number, + type: number, + connectionLength: number +) => { + return start + ((type >> 1) & 1) * connectionLength +} + +export const getPinPosition = ( + renderer: SimulationRenderer, + transform: Transform, + pin: PinWrapper +) => { + const { connectionLength } = renderer.options.gates + + // render little connection + const start = calculatePinStart( + transform, + pin.value.type, + renderer.options.gates.connectionLength + ) + + const height = calculatePinY(transform, pin.index, pin.total) + + const pinX = calculatePinx(start, pin.value.type, connectionLength) + const pinY = height + transform.y + + // rotate + const notRotated: vector2 = [pinX, pinY] + const rotated = rotateAroundVector( + notRotated, + transform.center, + transform.rotation + ) + + return rotated +} diff --git a/src/modules/simulationRenderer/helpers/projectPoint.ts b/src/modules/simulationRenderer/helpers/projectPoint.ts index 08f1acf..cbe1f70 100644 --- a/src/modules/simulationRenderer/helpers/projectPoint.ts +++ b/src/modules/simulationRenderer/helpers/projectPoint.ts @@ -1,4 +1,5 @@ -import { vector3, vector2 } from '../../simulation/classes/Transform' +import { vector3 } from '../../../common/math/types/vector3' +import { vector2 } from '../../../common/math/types/vector2' export const projectPointOnPlane = (point: vector3, light: vector3) => point.slice(0, 2).map((position, index) => { diff --git a/src/modules/simulationRenderer/helpers/renderClickedPins.ts b/src/modules/simulationRenderer/helpers/renderClickedPins.ts new file mode 100644 index 0000000..d6a3648 --- /dev/null +++ b/src/modules/simulationRenderer/helpers/renderClickedPins.ts @@ -0,0 +1,29 @@ +import { SimulationRenderer } from '../classes/SimulationRenderer' +import { getPinPosition } from './pinPosition' +import { SelectedPin } from '../types/SelectedPins' + +export const renderClickedPins = ( + ctx: CanvasRenderingContext2D, + renderer: SimulationRenderer +) => { + let pin: SelectedPin | null = null + + if (renderer.selectedPins.start) { + pin = renderer.selectedPins.start + } else if (renderer.selectedPins.end) { + pin = renderer.selectedPins.end + } + + if (pin) { + const position = getPinPosition(renderer, pin.transform, pin.wrapper) + + ctx.strokeStyle = 'yellow' + ctx.lineWidth = renderer.options.gates.pinRadius * 2 + ctx.lineCap = 'round' + + ctx.beginPath() + ctx.moveTo(...position) + ctx.lineTo(...renderer.lastMousePosition) + ctx.stroke() + } +} diff --git a/src/modules/simulationRenderer/helpers/renderGate.ts b/src/modules/simulationRenderer/helpers/renderGate.ts index 9efb6ee..338561d 100644 --- a/src/modules/simulationRenderer/helpers/renderGate.ts +++ b/src/modules/simulationRenderer/helpers/renderGate.ts @@ -1,7 +1,17 @@ import { Gate } from '../../simulation/classes/Gate' -import { drawRotatedSquare } from './drawRotatedSquare' +import { drawRotatedSquare } from '../../../common/canvas/helpers/drawRotatedSquare' +import { renderPins } from './renderPins' +import { SimulationRenderer } from '../classes/SimulationRenderer' -export const renderGate = (ctx: CanvasRenderingContext2D, gate: Gate) => { - ctx.fillStyle = gate.color - drawRotatedSquare(ctx, gate.transform) +export const renderGate = ( + ctx: CanvasRenderingContext2D, + renderer: SimulationRenderer, + gate: Gate +) => { + renderPins(ctx, renderer, gate) + + if (gate.template.material.type === 'color') { + ctx.fillStyle = gate.template.material.value + drawRotatedSquare(ctx, gate.transform, gate.template.shape) + } } diff --git a/src/modules/simulationRenderer/helpers/renderGateShadow.ts b/src/modules/simulationRenderer/helpers/renderGateShadow.ts deleted file mode 100644 index a102e96..0000000 --- a/src/modules/simulationRenderer/helpers/renderGateShadow.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Gate } from '../../simulation/classes/Gate' -import { projectPointOnPlane } from './projectPoint' -import { drawPolygon } from './drawPolygon' -import { vector3 } from '../../simulation/classes/Transform' - -export const renderGateShadow = ( - ctx: CanvasRenderingContext2D, - color: string, - gate: Gate, - gateHeight: number, - light: vector3 -) => { - ctx.fillStyle = color - - const points = gate.transform.getPoints() - const projections = points.map(point => - projectPointOnPlane([point[0], point[1], gateHeight], light) - ) - - drawPolygon(ctx, projections) -} diff --git a/src/modules/simulationRenderer/helpers/renderPins.ts b/src/modules/simulationRenderer/helpers/renderPins.ts new file mode 100644 index 0000000..8bff60b --- /dev/null +++ b/src/modules/simulationRenderer/helpers/renderPins.ts @@ -0,0 +1,53 @@ +import { Gate } from '../../simulation/classes/Gate' +import { SimulationRenderer } from '../classes/SimulationRenderer' +import { useTransform } from '../../../common/canvas/helpers/useTransform' +import { calculatePinStart, calculatePinY, calculatePinx } from './pinPosition' +import { pinFill } from './pinFill' + +export const renderPins = ( + ctx: CanvasRenderingContext2D, + renderer: SimulationRenderer, + gate: Gate +) => { + ctx.save() + + const { + connectionLength, + pinRadius, + pinStrokeColor, + pinStrokeWidth + } = renderer.options.gates + const relativeTransform = useTransform(ctx, gate.transform) + + ctx.strokeStyle = pinStrokeColor + ctx.lineWidth = pinStrokeWidth + + for (const pin of gate.pins) { + ctx.fillStyle = pinFill(pin.value) + + // render little connection + const start = calculatePinStart( + relativeTransform, + pin.value.type, + connectionLength + ) + + const height = calculatePinY(relativeTransform, pin.index, pin.total) + + const pinX = calculatePinx(start, pin.value.type, connectionLength) + const pinY = height + relativeTransform.y + + ctx.beginPath() + ctx.moveTo(start, pinY) + ctx.lineTo(start + connectionLength, pinY) + ctx.stroke() + + // render actual pin + ctx.beginPath() + ctx.ellipse(pinX, pinY, pinRadius, pinRadius, 0, 0, 2 * Math.PI) + ctx.fill() + ctx.stroke() + } + + ctx.restore() +} diff --git a/src/modules/simulationRenderer/helpers/renderSimulation.ts b/src/modules/simulationRenderer/helpers/renderSimulation.ts index 686067f..3d74aef 100644 --- a/src/modules/simulationRenderer/helpers/renderSimulation.ts +++ b/src/modules/simulationRenderer/helpers/renderSimulation.ts @@ -1,8 +1,8 @@ import { SimulationRenderer } from '../classes/SimulationRenderer' -import { relativeTo, invert } from '../../vector2/helpers/basic' -import { renderGateShadow } from './renderGateShadow' +import { invert } from '../../vector2/helpers/basic' import { renderGate } from './renderGate' -import { clearCanvas } from './clearCanvas' +import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas' +import { renderClickedPins } from './renderClickedPins' export const renderSimulation = ( ctx: CanvasRenderingContext2D, @@ -12,25 +12,12 @@ export const renderSimulation = ( ctx.translate(...renderer.camera.transform.position) - const center = relativeTo( - renderer.camera.transform.position, - renderer.screen.center - ) - // render gates for (const gate of renderer.simulation.gates) { - if (renderer.options.shadows.enabled) { - renderGateShadow( - ctx, - renderer.options.shadows.color, - gate, - renderer.options.shadows.gateHeight, - [center[0], center[1], renderer.options.shadows.lightHeight] - ) - } - - renderGate(ctx, gate) + renderGate(ctx, renderer, gate) } + renderClickedPins(ctx, renderer) + ctx.translate(...invert(renderer.camera.transform.position)) } diff --git a/src/modules/simulationRenderer/types/DeepPartial.ts b/src/modules/simulationRenderer/types/DeepPartial.ts new file mode 100644 index 0000000..491a933 --- /dev/null +++ b/src/modules/simulationRenderer/types/DeepPartial.ts @@ -0,0 +1,7 @@ +type DeepPartial = { + [P in keyof T]?: T[P] extends Array + ? Array> + : T[P] extends ReadonlyArray + ? ReadonlyArray> + : DeepPartial +} diff --git a/src/modules/simulationRenderer/types/SelectedPins.ts b/src/modules/simulationRenderer/types/SelectedPins.ts new file mode 100644 index 0000000..283e232 --- /dev/null +++ b/src/modules/simulationRenderer/types/SelectedPins.ts @@ -0,0 +1,12 @@ +import { Transform } from '../../../common/math/classes/Transform' +import { PinWrapper } from '../../simulation/classes/Gate' + +export interface SelectedPin { + wrapper: PinWrapper + transform: Transform +} + +export interface SelectedPins { + start: SelectedPin | null + end: SelectedPin | null +} diff --git a/src/modules/simulationRenderer/types/SimulationRendererOptions.ts b/src/modules/simulationRenderer/types/SimulationRendererOptions.ts index 4e6ce2d..bf471b8 100644 --- a/src/modules/simulationRenderer/types/SimulationRendererOptions.ts +++ b/src/modules/simulationRenderer/types/SimulationRendererOptions.ts @@ -1,11 +1,11 @@ export interface SimulationRendererOptions { - shadows: { - enabled: boolean - color: string - lightHeight: number - gateHeight: number - } dnd: { rotation: number } + gates: { + connectionLength: number + pinRadius: number + pinStrokeColor: string + pinStrokeWidth: number + } } diff --git a/src/modules/vector2/helpers/basic.ts b/src/modules/vector2/helpers/basic.ts index ba49c11..e9ef333 100644 --- a/src/modules/vector2/helpers/basic.ts +++ b/src/modules/vector2/helpers/basic.ts @@ -1,4 +1,4 @@ -import { vector2 } from '../../simulation/classes/Transform' +import { vector2 } from '../../../common/math/types/vector2' // Basic stuff for arrays diff --git a/src/modules/vector2/helpers/minmaxVector.ts b/src/modules/vector2/helpers/minmaxVector.ts index bf9933a..654006d 100644 --- a/src/modules/vector2/helpers/minmaxVector.ts +++ b/src/modules/vector2/helpers/minmaxVector.ts @@ -1,4 +1,4 @@ -import { vector2 } from '../../simulation/classes/Transform' +import { vector2 } from '../../../common/math/types/vector2' import { length } from './basic' export const minVector = (...vectors: vector2[]) => { diff --git a/src/modules/vector2/helpers/rotate.ts b/src/modules/vector2/helpers/rotate.ts index 5d465fd..4a4a7b5 100644 --- a/src/modules/vector2/helpers/rotate.ts +++ b/src/modules/vector2/helpers/rotate.ts @@ -1,4 +1,4 @@ -import { vector2 } from '../../simulation/classes/Transform' +import { vector2 } from '../../../common/math/types/vector2' import { add, invert } from './basic' const { cos, sin } = Math diff --git a/src/modules/vector2/helpers/smoothStep.ts b/src/modules/vector2/helpers/smoothStep.ts index d6a2901..8d0aef8 100644 --- a/src/modules/vector2/helpers/smoothStep.ts +++ b/src/modules/vector2/helpers/smoothStep.ts @@ -1,4 +1,4 @@ -import { vector2 } from '../../simulation/classes/Transform' +import { vector2 } from '../../../common/math/types/vector2' // TODO: rename export const smoothStep = (step: number, current: vector2, target: vector2) => {