import { Camera } from './Camera' import { Simulation } from '../../simulation/classes/Simulation' import { Subject } from 'rxjs' import { MouseEventInfo } from '../../core/components/FluidCanvas' 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<MouseEventInfo>() public mouseUpOutput = new Subject<MouseEventInfo>() public mouseMoveOutput = new Subject<MouseEventInfo>() // first bit = dragging // second bit = moving around private mouseState = 0b00 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<SimulationRendererOptions> = {}, public simulation = new Simulation() ) { this.options = merge(defaultSimulationRendererOptions, options) this.init() } public init() { this.mouseDownOutput.subscribe(event => { 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, pins } = gates[index] if (pointInSquare(worldPosition, transform)) { this.mouseManager.clear(worldPosition[0]) this.mouseState |= 1 this.movedSelection = false this.selectedGate = id this.gateSelectionOffset = worldPosition.map( (position, index) => position - transform.position[index] ) as vector2 const gateNode = this.simulation.gates.get(id) if (gateNode) { return this.simulation.gates.moveOnTop(gateNode) } 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.mouseState |= 2 }) this.mouseUpOutput.subscribe(event => { if (this.selectedGate !== null) { const selected = this.getSelected() if (selected) { selected.transform.rotation = 0 } this.selectedGate = null this.mouseState &= 0 } this.mouseState &= 0b00 }) this.mouseMoveOutput.subscribe(event => { const worldPosition = this.camera.toWordPostition(event.position) if (this.mouseState & 1 && this.selectedGate !== null) { const gate = this.getGateById(this.selectedGate) if (!gate || !gate.data) return const transform = gate.data.transform transform.x = worldPosition[0] - this.gateSelectionOffset[0] transform.y = worldPosition[1] - this.gateSelectionOffset[1] if (!this.movedSelection) { this.movedSelection = true } } if ((this.mouseState >> 1) & 1) { const offset = invert( relativeTo(this.lastMousePosition, worldPosition) ) this.camera.transform.position = add( this.camera.transform.position, invert(offset) ) } this.lastMousePosition = this.camera.toWordPostition(event.position) }) } public getGateById(id: number) { return this.simulation.gates.get(id) } public getSelected() { if (this.selectedGate === null) return null const gate = this.getGateById(this.selectedGate) if (!gate || !gate.data) return null return gate.data } }