fixed zomming and added 2 new delayers gate
This commit is contained in:
parent
2ce1919649
commit
f06fe88df5
6
src/assets/parallel.svg
Normal file
6
src/assets/parallel.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="800" height="800" fill="#FF7700"/>
|
||||||
|
<path d="M181 284H273.673L342.933 367H424.875L503.241 284H619" stroke="white" stroke-width="10"/>
|
||||||
|
<path d="M181 516H273.461L342.563 438H424.318L502.505 516H618" stroke="white" stroke-width="10"/>
|
||||||
|
<path d="M432.5 400C432.5 429.426 410.144 452.5 383.5 452.5C356.856 452.5 334.5 429.426 334.5 400C334.5 370.574 356.856 347.5 383.5 347.5C410.144 347.5 432.5 370.574 432.5 400Z" fill="#FF7700" stroke="white" stroke-width="15"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 579 B |
7
src/assets/sequential.svg
Normal file
7
src/assets/sequential.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="800" height="800" fill="#FF7700"/>
|
||||||
|
<path d="M171 274H274.688L352.182 362.006H443.864L533 274V404" stroke="white" stroke-width="10"/>
|
||||||
|
<path d="M171 526H274.674L352.157 444.355H443.827L582 526V409" stroke="white" stroke-width="10"/>
|
||||||
|
<circle cx="398.5" cy="403.5" r="56" fill="#FF7700" stroke="white" stroke-width="15"/>
|
||||||
|
<path d="M457.693 407.304L629.998 411.373" stroke="white" stroke-width="10"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 516 B |
72
src/modules/activation/classes/ExecutionQueue.ts
Normal file
72
src/modules/activation/classes/ExecutionQueue.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { Subject } from 'rxjs'
|
||||||
|
import { filter, take } from 'rxjs/operators'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of what a task should do and where the output should be delivered
|
||||||
|
*/
|
||||||
|
export interface Task<T> {
|
||||||
|
output: Subject<T>
|
||||||
|
execute: () => Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to execute a number of async tasks
|
||||||
|
*/
|
||||||
|
export class ExecutionQueue<T> {
|
||||||
|
/**
|
||||||
|
* An array of all the tasks wich need to be executed
|
||||||
|
*/
|
||||||
|
private tasks: Task<T>[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of the current task
|
||||||
|
*/
|
||||||
|
private current: Promise<T> | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wheather the tasks should continue beeing executed
|
||||||
|
*/
|
||||||
|
public active = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new task to the queue
|
||||||
|
*
|
||||||
|
* @param task The task to add
|
||||||
|
*/
|
||||||
|
public push(task: () => Promise<T>) {
|
||||||
|
const executionSubject = new Subject<T>()
|
||||||
|
const executionPromise = executionSubject.pipe(take(1)).toPromise()
|
||||||
|
|
||||||
|
this.tasks.push({
|
||||||
|
output: executionSubject,
|
||||||
|
execute: task
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!this.current) {
|
||||||
|
this.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return executionPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the next task in the queue
|
||||||
|
*/
|
||||||
|
private next() {
|
||||||
|
const task = this.tasks.shift()
|
||||||
|
|
||||||
|
if (task) {
|
||||||
|
this.current = task.execute()
|
||||||
|
|
||||||
|
this.current.then(result => {
|
||||||
|
task.output.next(result)
|
||||||
|
|
||||||
|
if (this.active) {
|
||||||
|
this.next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -243,6 +243,49 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
count: 3
|
count: 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'Sequential delayer'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
type: 'image',
|
||||||
|
fill: require('../../assets/sequential')
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `
|
||||||
|
const i = context.get(0)
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res()
|
||||||
|
},1000)
|
||||||
|
}).then(() => {
|
||||||
|
context.set(0,i)
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
async: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'Parallel delayer'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
type: 'image',
|
||||||
|
fill: require('../../assets/parallel')
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `
|
||||||
|
const i = context.get(0)
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res()
|
||||||
|
},1000)
|
||||||
|
}).then(() => {
|
||||||
|
context.set(0,i)
|
||||||
|
})
|
||||||
|
`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ export const getGateState = (gate: Gate): GateState => {
|
||||||
return {
|
return {
|
||||||
id: gate.id,
|
id: gate.id,
|
||||||
template: gate.template.metadata.name,
|
template: gate.template.metadata.name,
|
||||||
transform: getTransformState(gate.transform)
|
transform: getTransformState(gate.transform),
|
||||||
|
props: gate.props
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ export interface GateState {
|
||||||
transform: TransformState
|
transform: TransformState
|
||||||
id: number
|
id: number
|
||||||
template: string
|
template: string
|
||||||
|
props: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CameraState {
|
export interface CameraState {
|
||||||
|
|
|
@ -3,11 +3,6 @@ import { copy } from './copy'
|
||||||
import { paste } from './paste'
|
import { paste } from './paste'
|
||||||
|
|
||||||
export const duplicate = (renderer: SimulationRenderer) => {
|
export const duplicate = (renderer: SimulationRenderer) => {
|
||||||
const { clipboard, wireClipboard } = renderer
|
|
||||||
|
|
||||||
copy(renderer)
|
copy(renderer)
|
||||||
paste(renderer)
|
paste(renderer)
|
||||||
|
|
||||||
renderer.clipboard = clipboard
|
|
||||||
renderer.wireClipboard = wireClipboard
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,50 +14,123 @@ import { fromSimulationState } from '../../saving/helpers/fromState'
|
||||||
import { saveStore } from '../../saving/stores/saveStore'
|
import { saveStore } from '../../saving/stores/saveStore'
|
||||||
import { Wire } from './Wire'
|
import { Wire } from './Wire'
|
||||||
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
|
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
|
||||||
|
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface for the pins of a gate
|
||||||
|
*/
|
||||||
export interface GatePins {
|
export interface GatePins {
|
||||||
inputs: Pin[]
|
inputs: Pin[]
|
||||||
outputs: Pin[]
|
outputs: Pin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around a pin so it can be rendered at the right place
|
||||||
|
*/
|
||||||
export interface PinWrapper {
|
export interface PinWrapper {
|
||||||
total: number
|
total: number
|
||||||
index: number
|
index: number
|
||||||
value: Pin
|
value: Pin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function wich can be run with an activation context
|
||||||
|
*/
|
||||||
export type GateFunction = null | ((ctx: Context) => void)
|
export type GateFunction = null | ((ctx: Context) => void)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All functions a gate must remember
|
||||||
|
*/
|
||||||
export interface GateFunctions {
|
export interface GateFunctions {
|
||||||
activation: GateFunction
|
activation: GateFunction
|
||||||
onClick: GateFunction
|
onClick: GateFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Gate {
|
export class Gate {
|
||||||
|
/**
|
||||||
|
* The transform of the gate
|
||||||
|
*/
|
||||||
public transform = new Transform()
|
public transform = new Transform()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object holding all the pins the gate curently has
|
||||||
|
*/
|
||||||
public _pins: GatePins = {
|
public _pins: GatePins = {
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: []
|
outputs: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the gate
|
||||||
|
*/
|
||||||
public id: number
|
public id: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template the gate needs to follow
|
||||||
|
*/
|
||||||
public template: GateTemplate
|
public template: GateTemplate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the functions created from the template strings
|
||||||
|
*/
|
||||||
private functions: GateFunctions = {
|
private functions: GateFunctions = {
|
||||||
activation: null,
|
activation: null,
|
||||||
onClick: null
|
onClick: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used only if the gate is async
|
||||||
|
*/
|
||||||
|
private executionQueue = new ExecutionQueue<void>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All rxjs subscriptions the gate created
|
||||||
|
* (if they are not manually cleared it can lead to memory leaks)
|
||||||
|
*/
|
||||||
private subscriptions: Subscription[] = []
|
private subscriptions: Subscription[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state the activation functions have aces to
|
||||||
|
*/
|
||||||
private memory: Record<string, unknown> = {}
|
private memory: Record<string, unknown> = {}
|
||||||
|
|
||||||
// Related to integration
|
/**
|
||||||
|
* The inner simulaton used by integrated circuits
|
||||||
|
*/
|
||||||
private ghostSimulation: Simulation
|
private ghostSimulation: Simulation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wires connecting the outer simulation to the inner one
|
||||||
|
*/
|
||||||
private ghostWires: Wire[] = []
|
private ghostWires: Wire[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean keeping track if the component is an ic
|
||||||
|
*/
|
||||||
private isIntegrated = false
|
private isIntegrated = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to know if the component runs in the global scope (rendered)
|
||||||
|
* or insie an integrated circuit
|
||||||
|
*/
|
||||||
public env: SimulationEnv = 'global'
|
public env: SimulationEnv = 'global'
|
||||||
|
|
||||||
public constructor(template: DeepPartial<GateTemplate> = {}, id?: number) {
|
/**
|
||||||
|
* The props used by the activation function (the same as memory but presists)
|
||||||
|
*/
|
||||||
|
public props: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main logic gate class
|
||||||
|
*
|
||||||
|
* @param template The template the gate needs to follow
|
||||||
|
* @param id The id of the gate
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
template: DeepPartial<GateTemplate> = {},
|
||||||
|
id?: number,
|
||||||
|
props: Record<string, unknown> = {}
|
||||||
|
) {
|
||||||
this.template = completeTemplate(template)
|
this.template = completeTemplate(template)
|
||||||
|
|
||||||
this.transform.scale = this.template.shape.scale
|
this.transform.scale = this.template.shape.scale
|
||||||
|
@ -97,7 +170,13 @@ export class Gate {
|
||||||
const pipes = getGateTimePipes(this.template)
|
const pipes = getGateTimePipes(this.template)
|
||||||
|
|
||||||
const subscription = pin.state.pipe(...pipes).subscribe(() => {
|
const subscription = pin.state.pipe(...pipes).subscribe(() => {
|
||||||
|
if (this.template.code.async) {
|
||||||
|
this.executionQueue.push(async () => {
|
||||||
|
return await this.update()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
this.update()
|
this.update()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.subscriptions.push(subscription)
|
this.subscriptions.push(subscription)
|
||||||
|
@ -173,8 +252,29 @@ export class Gate {
|
||||||
|
|
||||||
this.ghostSimulation.wires.push(...this.ghostWires)
|
this.ghostSimulation.wires.push(...this.ghostWires)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.assignProps(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign the props passed to the gate and mere them with the base ones
|
||||||
|
*/
|
||||||
|
private assignProps(props: Record<string, unknown>) {
|
||||||
|
if (this.template.properties.enabled) {
|
||||||
|
for (const prop in this.template.properties) {
|
||||||
|
if (prop !== 'enabled') {
|
||||||
|
this.props[prop] =
|
||||||
|
props[prop] !== undefined
|
||||||
|
? props[prop]
|
||||||
|
: this.template.properties[prop].base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the init function from the template
|
||||||
|
*/
|
||||||
private init() {
|
private init() {
|
||||||
toFunction<[InitialisationContext]>(
|
toFunction<[InitialisationContext]>(
|
||||||
this.template.code.initialisation,
|
this.template.code.initialisation,
|
||||||
|
@ -184,12 +284,18 @@ export class Gate {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the onClick function from the template
|
||||||
|
*/
|
||||||
public onClick() {
|
public onClick() {
|
||||||
if (this.functions.onClick) {
|
if (this.functions.onClick) {
|
||||||
this.functions.onClick(this.getContext())
|
this.functions.onClick(this.getContext())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears subscriptions to prevent memory leaks
|
||||||
|
*/
|
||||||
public dispose() {
|
public dispose() {
|
||||||
for (const pin of this.pins) {
|
for (const pin of this.pins) {
|
||||||
pin.value.dispose()
|
pin.value.dispose()
|
||||||
|
@ -204,18 +310,23 @@ export class Gate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the activation function from the template
|
||||||
|
*/
|
||||||
public update() {
|
public update() {
|
||||||
if (this.template.tags.includes('integrated')) {
|
if (!this.template.tags.includes('integrated')) {
|
||||||
} else {
|
|
||||||
const context = this.getContext()
|
const context = this.getContext()
|
||||||
|
|
||||||
if (!this.functions.activation)
|
if (!this.functions.activation)
|
||||||
throw new SimulationError('Activation function is missing')
|
throw new SimulationError('Activation function is missing')
|
||||||
|
|
||||||
this.functions.activation(context)
|
return this.functions.activation(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the activation context
|
||||||
|
*/
|
||||||
public getContext(): Context {
|
public getContext(): Context {
|
||||||
return {
|
return {
|
||||||
get: (index: number) => {
|
get: (index: number) => {
|
||||||
|
@ -237,7 +348,11 @@ export class Gate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private wrapPins(pins: Pin[]) {
|
/**
|
||||||
|
* Generates pin wrappers from an array of pins
|
||||||
|
*
|
||||||
|
* @param pins The pins to wwap
|
||||||
|
*/ private wrapPins(pins: Pin[]) {
|
||||||
const result: PinWrapper[] = []
|
const result: PinWrapper[] = []
|
||||||
const length = pins.length
|
const length = pins.length
|
||||||
|
|
||||||
|
@ -252,6 +367,9 @@ export class Gate {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all pins (input + output)
|
||||||
|
*/
|
||||||
public get pins() {
|
public get pins() {
|
||||||
const result = [
|
const result = [
|
||||||
...this.wrapPins(this._pins.inputs),
|
...this.wrapPins(this._pins.inputs),
|
||||||
|
@ -261,6 +379,9 @@ export class Gate {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates empty pins for any gate
|
||||||
|
*/
|
||||||
private static generatePins(options: PinCount, type: number, gate: Gate) {
|
private static generatePins(options: PinCount, type: number, gate: Gate) {
|
||||||
return [...Array(options.count)]
|
return [...Array(options.count)]
|
||||||
.fill(true)
|
.fill(true)
|
||||||
|
|
|
@ -29,6 +29,7 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
scale: [100, 100]
|
scale: [100, 100]
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
|
async: false,
|
||||||
activation: '',
|
activation: '',
|
||||||
onClick: '',
|
onClick: '',
|
||||||
initialisation: ''
|
initialisation: ''
|
||||||
|
@ -48,5 +49,8 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
output: false
|
output: false
|
||||||
},
|
},
|
||||||
info: [],
|
info: [],
|
||||||
tags: ['base']
|
tags: ['base'],
|
||||||
|
properties: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import { vector2 } from '../../../common/math/classes/Transform'
|
import { vector2 } from '../../../common/math/classes/Transform'
|
||||||
|
import { InputHTMLAttributes } from 'react'
|
||||||
|
|
||||||
export interface PinCount {
|
export interface PinCount {
|
||||||
variable: boolean
|
variable: boolean
|
||||||
count: number
|
count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Property<T> {
|
||||||
|
type: HTMLInputElement['type']
|
||||||
|
encode: (value: string) => T
|
||||||
|
base: T
|
||||||
|
}
|
||||||
|
|
||||||
export interface Material {
|
export interface Material {
|
||||||
type: 'color' | 'image'
|
type: 'color' | 'image'
|
||||||
fill: string
|
fill: string
|
||||||
|
@ -46,6 +53,7 @@ export interface GateTemplate {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
code: {
|
code: {
|
||||||
|
async: boolean
|
||||||
initialisation: string
|
initialisation: string
|
||||||
activation: string
|
activation: string
|
||||||
onClick: string
|
onClick: string
|
||||||
|
@ -61,4 +69,5 @@ export interface GateTemplate {
|
||||||
}
|
}
|
||||||
info: string[]
|
info: string[]
|
||||||
tags: GateTag[]
|
tags: GateTag[]
|
||||||
|
properties: Enabled<Record<Exclude<string, 'enabled'>, Property<unknown>>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
fromCameraState
|
fromCameraState
|
||||||
} from '../../saving/helpers/fromState'
|
} from '../../saving/helpers/fromState'
|
||||||
import merge from 'deepmerge'
|
import merge from 'deepmerge'
|
||||||
import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
|
import { handleScroll } from '../helpers/scaleCanvas'
|
||||||
import { RefObject } from 'react'
|
import { RefObject } from 'react'
|
||||||
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
||||||
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
|
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
|
||||||
|
@ -55,7 +55,7 @@ export class SimulationRenderer {
|
||||||
public wireClipboard: WireState[] = []
|
public wireClipboard: WireState[] = []
|
||||||
|
|
||||||
// first bit = dragging
|
// first bit = dragging
|
||||||
// second bit = panning around
|
// second bit = panning
|
||||||
// third bit = selecting
|
// third bit = selecting
|
||||||
public mouseState = 0b000
|
public mouseState = 0b000
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ export class SimulationRenderer {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseUpOutput.subscribe(event => {
|
this.mouseUpOutput.subscribe(event => {
|
||||||
if (event.button === mouseButtons.drag) {
|
if (event.button === mouseButtons.drag && this.mouseState & 1) {
|
||||||
const selected = this.getSelected()
|
const selected = this.getSelected()
|
||||||
|
|
||||||
for (const gate of selected) {
|
for (const gate of selected) {
|
||||||
|
@ -209,8 +209,8 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
this.selectedGates.temporary.clear()
|
this.selectedGates.temporary.clear()
|
||||||
|
|
||||||
// turn first 2 bits to 0
|
// turn first bit to 0
|
||||||
this.mouseState &= 1 << 2
|
this.mouseState &= 6
|
||||||
|
|
||||||
// for debugging
|
// for debugging
|
||||||
if ((this.mouseState >> 1) & 1 || this.mouseState & 1) {
|
if ((this.mouseState >> 1) & 1 || this.mouseState & 1) {
|
||||||
|
@ -221,11 +221,16 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.button === mouseButtons.select &&
|
event.button === mouseButtons.pan &&
|
||||||
(this.mouseState >> 2) & 1
|
(this.mouseState >> 1) & 1
|
||||||
) {
|
) {
|
||||||
|
// turn second bit to 0
|
||||||
|
this.mouseState &= 5
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.button === mouseButtons.select && this.mouseState >> 2) {
|
||||||
// turn the third bit to 0
|
// turn the third bit to 0
|
||||||
this.mouseState &= (1 << 2) - 1
|
this.mouseState &= 3
|
||||||
|
|
||||||
const selectedGates = gatesInSelection(
|
const selectedGates = gatesInSelection(
|
||||||
this.selectedArea,
|
this.selectedArea,
|
||||||
|
@ -249,8 +254,6 @@ export class SimulationRenderer {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseMoveOutput.subscribe(event => {
|
this.mouseMoveOutput.subscribe(event => {
|
||||||
updateMouse(event)
|
|
||||||
|
|
||||||
const worldPosition = this.camera.toWordPostition(event.position)
|
const worldPosition = this.camera.toWordPostition(event.position)
|
||||||
|
|
||||||
const offset = invert(
|
const offset = invert(
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const mouseButtons: Record<
|
||||||
mouseButton
|
mouseButton
|
||||||
> = {
|
> = {
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
drag: 2,
|
drag: 0,
|
||||||
pan: 2,
|
pan: 2,
|
||||||
select: 0,
|
select: 0,
|
||||||
unselect: 0
|
unselect: 0
|
||||||
|
|
|
@ -1,37 +1,47 @@
|
||||||
import { clamp } from '../../simulation/helpers/clamp'
|
import { clamp } from '../../simulation/helpers/clamp'
|
||||||
import { Camera } from '../classes/Camera'
|
import { Camera } from '../classes/Camera'
|
||||||
import { vector2 } from '../../../common/math/classes/Transform'
|
import { vector2 } from '../../../common/math/classes/Transform'
|
||||||
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
import { multiply, substract } from '../../vector2/helpers/basic'
|
||||||
import { Screen } from '../../screen/helpers/Screen'
|
import { repeat } from '../../vector2/helpers/repeat'
|
||||||
|
|
||||||
const scrollStep = 1.3
|
const scrollStep = 1.3
|
||||||
const zoomLimits = [0.1, 10]
|
const zoomLimits = [0.1, 10]
|
||||||
|
|
||||||
let absoluteMousePosition = [Screen.width / 2, Screen.height]
|
/*
|
||||||
|
f(x) = (a - x) / b(x)
|
||||||
|
|
||||||
export const updateMouse = (e: MouseEventInfo) => {
|
f(x0) = f(x1)
|
||||||
absoluteMousePosition = e.position
|
|
||||||
}
|
|
||||||
|
|
||||||
|
(a - x0) / b(x0) = (a - x1) / b(x1)
|
||||||
|
|
||||||
|
b = b(x1) / b(x0)
|
||||||
|
|
||||||
|
b * (a - x0) = a - x1
|
||||||
|
x1 = a - b * (a - x0)
|
||||||
|
*/
|
||||||
export const handleScroll = (e: WheelEvent, camera: Camera) => {
|
export const handleScroll = (e: WheelEvent, camera: Camera) => {
|
||||||
const sign = -e.deltaY / Math.abs(e.deltaY)
|
const sign = -e.deltaY / Math.abs(e.deltaY)
|
||||||
const zoom = scrollStep ** sign
|
const zoom = scrollStep ** sign
|
||||||
|
|
||||||
const mouseFraction = Screen.scale.map(
|
if (!e.deltaY) {
|
||||||
(value, index) => absoluteMousePosition[index] / value
|
return
|
||||||
)
|
}
|
||||||
const newScale = camera.transform.scale.map(value =>
|
|
||||||
clamp(zoomLimits[0], zoomLimits[1], value * zoom)
|
const { position, scale } = camera.transform
|
||||||
)
|
|
||||||
const delta = camera.transform.scale.map(
|
const mousePosition = [e.clientX, e.clientY]
|
||||||
(value, index) =>
|
const oldPosition = [...mousePosition] as vector2
|
||||||
Screen.scale[index] *
|
|
||||||
(newScale[index] - value) *
|
const oldScale = scale[0]
|
||||||
mouseFraction[index]
|
const newScale = clamp(zoomLimits[0], zoomLimits[1], oldScale * zoom)
|
||||||
|
|
||||||
|
camera.transform.scale = repeat(newScale, 2) as vector2
|
||||||
|
|
||||||
|
const scaleFraction = newScale / oldScale
|
||||||
|
const newPosition = substract(
|
||||||
|
oldPosition,
|
||||||
|
multiply(substract(oldPosition, position), scaleFraction)
|
||||||
)
|
)
|
||||||
|
|
||||||
camera.transform.scale = newScale as vector2
|
camera.transform.position = newPosition
|
||||||
camera.transform.position = camera.transform.position.map(
|
|
||||||
(value, index) => value - delta[index]
|
|
||||||
) as vector2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,15 @@ export const length = (vector: vector2) =>
|
||||||
export const multiply = (vector: vector2, scalar: number) =>
|
export const multiply = (vector: vector2, scalar: number) =>
|
||||||
vector.map(val => val * scalar) as vector2
|
vector.map(val => val * scalar) as vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies 2 vectors
|
||||||
|
*
|
||||||
|
* @param vector The first vector to multiply
|
||||||
|
* @param other The second vector to multiply
|
||||||
|
*/
|
||||||
|
export const multiplyVectors = (vector: vector2, other: vector2) =>
|
||||||
|
vector.map((position, index) => position * other[index]) as vector2
|
||||||
|
|
||||||
// This makese the length of the vector 1
|
// This makese the length of the vector 1
|
||||||
export const normalise = (vector: vector2) => {
|
export const normalise = (vector: vector2) => {
|
||||||
const size = length(vector)
|
const size = length(vector)
|
||||||
|
@ -35,8 +44,22 @@ export const ofLength = (vector: vector2, l: number) => {
|
||||||
return multiply(vector, l / length(vector))
|
return multiply(vector, l / length(vector))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This returns a vector relative to the other
|
/**
|
||||||
|
* Moves a vector relative to another
|
||||||
|
*
|
||||||
|
* @param vector The vector t move relative to something
|
||||||
|
* @param other The vector for the first one to be mvoed relative to
|
||||||
|
*/
|
||||||
export const relativeTo = (vector: vector2, other: vector2) =>
|
export const relativeTo = (vector: vector2, other: vector2) =>
|
||||||
add(other, invert(vector))
|
add(other, invert(vector))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts a vector from another
|
||||||
|
*
|
||||||
|
* @param vector Hhe vector to substruact from
|
||||||
|
* @param other The vector to subtract
|
||||||
|
*/
|
||||||
|
export const substract = (vector: vector2, other: vector2) =>
|
||||||
|
relativeTo(other, vector)
|
||||||
|
|
||||||
export const inverse = (vector: vector2) => vector.map(a => 1 / a) as vector2
|
export const inverse = (vector: vector2) => vector.map(a => 1 / a) as vector2
|
||||||
|
|
Loading…
Reference in a new issue