added half and full adders
This commit is contained in:
parent
285248435a
commit
120e4116b2
10
src/assets/full-adder.svg
Normal file
10
src/assets/full-adder.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M800 0H0V800H800V0Z" fill="#00B512"/>
|
||||
<path d="M398 295L396.522 504C322.104 504 289 469.556 289 401.327C289 333.097 324.731 295 398 295Z" fill="white" stroke="white" stroke-width="10"/>
|
||||
<path d="M400 295L401.142 504C458.653 504 511 471.73 511 403.5C511 335.27 456.624 295 400 295Z" fill="white" stroke="white" stroke-width="10"/>
|
||||
<path d="M416 333H386V390H315V416.5H386V466H416V416.5H481.5V390H416V333Z" fill="#00B512" stroke="white" stroke-width="10"/>
|
||||
<path d="M511 400H597.5" stroke="white" stroke-width="10"/>
|
||||
<path d="M228 333H314.5" stroke="white" stroke-width="10"/>
|
||||
<path d="M202 400H288.5" stroke="white" stroke-width="10"/>
|
||||
<path d="M227 464H313.5" stroke="white" stroke-width="10"/>
|
||||
</svg>
|
After Width: | Height: | Size: 810 B |
9
src/assets/half-adder.svg
Normal file
9
src/assets/half-adder.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<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="#00B512"/>
|
||||
<path d="M398 295L396.522 504C322.104 504 289 469.556 289 401.327C289 333.097 324.731 295 398 295Z" fill="white" stroke="white" stroke-width="10"/>
|
||||
<path d="M400 295L401.142 504C458.653 504 511 471.73 511 403.5C511 335.27 456.624 295 400 295Z" stroke="white" stroke-width="10"/>
|
||||
<path d="M416 333H386V390H315V416.5H386V466H416V416.5H481.5V390H416V333Z" fill="#00B512" stroke="white" stroke-width="10"/>
|
||||
<path d="M511 400H597.5" stroke="white" stroke-width="10"/>
|
||||
<path d="M228 333H314.5" stroke="white" stroke-width="10"/>
|
||||
<path d="M227 464H313.5" stroke="white" stroke-width="10"/>
|
||||
</svg>
|
After Width: | Height: | Size: 738 B |
|
@ -1,12 +1,17 @@
|
|||
import { allCombinations } from '../../../modules/simulation/helpers/allCombinations'
|
||||
import { rotateAroundVector } from '../../../modules/vector2/helpers/rotate'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { vector2 } from '../types/vector2'
|
||||
|
||||
export class Transform {
|
||||
public positionSubject = new BehaviorSubject<vector2>([0, 0])
|
||||
|
||||
public constructor(
|
||||
public position: vector2 = [0, 0],
|
||||
public _position: vector2 = [0, 0],
|
||||
public scale: vector2 = [1, 1],
|
||||
public rotation = 0
|
||||
) {}
|
||||
) {
|
||||
this.updatePositionSubject()
|
||||
}
|
||||
|
||||
public getBoundingBox() {
|
||||
const result = [...this.position, ...this.scale] as vector4
|
||||
|
@ -37,6 +42,29 @@ export class Transform {
|
|||
return edges as [vector2, vector2][]
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the current position trough the position subject
|
||||
*/
|
||||
private updatePositionSubject() {
|
||||
this.positionSubject.next(this.position)
|
||||
}
|
||||
|
||||
/**
|
||||
* getter for the position
|
||||
*/
|
||||
get position() {
|
||||
return this._position
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for the position
|
||||
*/
|
||||
set position(value: vector2) {
|
||||
this._position = value
|
||||
|
||||
this.updatePositionSubject()
|
||||
}
|
||||
|
||||
/** Short forms for random stuff */
|
||||
|
||||
get x() {
|
||||
|
@ -77,10 +105,14 @@ export class Transform {
|
|||
|
||||
set x(value: number) {
|
||||
this.position = [value, this.y]
|
||||
|
||||
this.updatePositionSubject()
|
||||
}
|
||||
|
||||
set y(value: number) {
|
||||
this.position = [this.x, value]
|
||||
|
||||
this.updatePositionSubject()
|
||||
}
|
||||
|
||||
set width(value: number) {
|
||||
|
@ -92,7 +124,6 @@ export class Transform {
|
|||
}
|
||||
}
|
||||
|
||||
export type vector2 = [number, number]
|
||||
export type vector3 = [number, number, number]
|
||||
export type vector4 = [number, number, number, number]
|
||||
export type vector8 = [
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface Context {
|
|||
get: (index: number) => boolean
|
||||
set: (index: number, state: boolean) => void
|
||||
color: (color: string) => void
|
||||
innerText: (value: string) => void
|
||||
update: () => void
|
||||
enviroment: SimulationEnv
|
||||
colors: Record<string, string>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
|
||||
import { useObservable } from 'rxjs-hooks'
|
||||
import { Subject } from 'rxjs'
|
||||
import { mouseButton } from '../types/mouseButton'
|
||||
import { MouseEventInfo } from './MouseEventInfo'
|
||||
import { MouseEventInfo } from '../types/MouseEventInfo'
|
||||
import { width, height } from '../../screen/helpers/Screen'
|
||||
import { getEventInfo } from '../helpers/getEventInfo'
|
||||
|
||||
export interface FluidCanvasProps {
|
||||
mouseDownOuput: Subject<MouseEventInfo>
|
||||
|
@ -11,15 +11,6 @@ export interface FluidCanvasProps {
|
|||
mouseMoveOutput: Subject<MouseEventInfo>
|
||||
}
|
||||
|
||||
export const getEventInfo = (
|
||||
e: MouseEvent<HTMLCanvasElement>
|
||||
): MouseEventInfo => {
|
||||
return {
|
||||
button: e.button as mouseButton,
|
||||
position: [e.clientX, e.clientY]
|
||||
}
|
||||
}
|
||||
|
||||
export const mouseEventHandler = (output: Subject<MouseEventInfo>) => (
|
||||
e: MouseEvent<HTMLCanvasElement>
|
||||
) => {
|
||||
|
|
17
src/modules/core/helpers/getEventInfo.ts
Normal file
17
src/modules/core/helpers/getEventInfo.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { MouseEvent } from 'react'
|
||||
import { MouseEventInfo } from '../types/MouseEventInfo'
|
||||
import { mouseButton } from '../types/mouseButton'
|
||||
|
||||
/**
|
||||
* Extracts the bareminimum from a mouseEvent instance
|
||||
*
|
||||
* @param e The MouseEvent instance
|
||||
*/
|
||||
export const getEventInfo = (
|
||||
e: MouseEvent<HTMLCanvasElement | HTMLDivElement>
|
||||
): MouseEventInfo => {
|
||||
return {
|
||||
button: e.button as mouseButton,
|
||||
position: [e.clientX, e.clientY]
|
||||
}
|
||||
}
|
|
@ -1,2 +1,6 @@
|
|||
@import './mui-overrides.scss';
|
||||
@import './toasts.scss';
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
import { mouseButton } from '../types/mouseButton'
|
||||
import { mouseButton } from './mouseButton'
|
||||
|
||||
/**
|
||||
* The info about the mouse passed to mouse subjects
|
|
@ -1,4 +1,4 @@
|
|||
import { Subject } from 'rxjs'
|
||||
import { MouseEventInfo } from '../components/MouseEventInfo'
|
||||
import { MouseEventInfo } from './MouseEventInfo'
|
||||
|
||||
export type MouseSubject = Subject<MouseEventInfo>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import './GateProperties.scss'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import React, { ChangeEvent, MouseEvent } from 'react'
|
||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||
import { Property } from '../../simulation/types/GateTemplate'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { useObservable } from 'rxjs-hooks'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
|
@ -12,8 +11,6 @@ import { Gate } from '../../simulation/classes/Gate'
|
|||
|
||||
export interface GatePropertyProps {
|
||||
raw: Property
|
||||
name: string
|
||||
output: BehaviorSubject<string | number | boolean>
|
||||
gate: Gate
|
||||
}
|
||||
|
||||
|
@ -22,8 +19,10 @@ export interface GatePropertyProps {
|
|||
*
|
||||
* @param param0 The props passed to the component
|
||||
*/
|
||||
export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
||||
const outputSnapshot = useObservable(() => output, '')
|
||||
export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
|
||||
const { name } = raw
|
||||
const prop = gate.props[name]
|
||||
const outputSnapshot = useObservable(() => prop, '')
|
||||
|
||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}:`
|
||||
|
||||
|
@ -37,10 +36,8 @@ export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
|||
value = Number(target.value)
|
||||
}
|
||||
|
||||
gate.props[name] = value
|
||||
|
||||
if (raw.type !== 'boolean') {
|
||||
output.next(target.value)
|
||||
prop.next(value)
|
||||
}
|
||||
|
||||
if (raw.needsUpdate === true) {
|
||||
|
@ -49,13 +46,16 @@ export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
|||
}
|
||||
|
||||
let input = <></>
|
||||
if (raw.type === 'number' || raw.type === 'text') {
|
||||
|
||||
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
|
||||
input = (
|
||||
<TextField
|
||||
onChange={handleChange}
|
||||
label={displayableName}
|
||||
value={outputSnapshot}
|
||||
type={raw.type}
|
||||
multiline={raw.type === 'string'}
|
||||
rowsMax={7}
|
||||
/>
|
||||
)
|
||||
} else if (raw.type === 'boolean') {
|
||||
|
@ -64,7 +64,7 @@ export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
|||
<span className="checkbox-label">{displayableName}</span>
|
||||
<CheckBox
|
||||
onClick={() => {
|
||||
output.next(!outputSnapshot)
|
||||
prop.next(!outputSnapshot)
|
||||
}}
|
||||
onChange={handleChange}
|
||||
checked={!!outputSnapshot}
|
||||
|
@ -93,7 +93,6 @@ const GateProperties = () => {
|
|||
}
|
||||
|
||||
const gate = node.data
|
||||
const gateProps = gate.props
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -113,20 +112,11 @@ const GateProperties = () => {
|
|||
<Divider id="gate-props-divider" />
|
||||
|
||||
{gate.template.properties.data.map((raw, index) => {
|
||||
const { name, base } = raw
|
||||
|
||||
const prop = gateProps[name]
|
||||
const output = new BehaviorSubject(
|
||||
gateProps.hasOwnProperty(name) ? prop : base
|
||||
)
|
||||
|
||||
return (
|
||||
<GatePropery
|
||||
output={output}
|
||||
raw={raw}
|
||||
name={name}
|
||||
gate={gate}
|
||||
key={`${index}-${id.value}-${output.value}`}
|
||||
key={`${index}-${id.value}`}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BehaviorSubject, Subject } from 'rxjs'
|
||||
|
||||
export const open = new Subject<boolean>()
|
||||
export const open = new BehaviorSubject<boolean>(false)
|
||||
export const id = new BehaviorSubject<number>(0)
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import { InputStore } from '../../input/stores/InputStore'
|
||||
import { CreateSimulationStore } from '../../create-simulation/stores/CreateSimulationStore'
|
||||
import { open as propModalIsOpen } from '../../logic-gates/subjects/LogicGatePropsSubjects'
|
||||
|
||||
/**
|
||||
* Returns true if any modal is open
|
||||
*/
|
||||
export const modalIsOpen = () => {
|
||||
return InputStore.data.open.value || CreateSimulationStore.data.open.value
|
||||
return (
|
||||
InputStore.data.open.value ||
|
||||
CreateSimulationStore.data.open.value ||
|
||||
propModalIsOpen.value
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import rgbLightTemplate from './templates/rgb'
|
|||
import sequentialDelayerTemplate from './templates/sequentialDelayer'
|
||||
import xnorTemplate from './templates/xnor'
|
||||
import xorTemplate from './templates/xor'
|
||||
import halfAdderTemplate from './templates/halfAdder'
|
||||
import fullAdderTemplate from './templates/fullAdder'
|
||||
|
||||
export const defaultSimulationName = 'default'
|
||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||
|
@ -26,7 +28,10 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
|||
rgbLightTemplate,
|
||||
sequentialDelayerTemplate,
|
||||
xnorTemplate,
|
||||
xorTemplate
|
||||
xorTemplate,
|
||||
halfAdderTemplate,
|
||||
fullAdderTemplate
|
||||
// commentTemplate
|
||||
]
|
||||
|
||||
export const baseSave: RendererState = {
|
||||
|
|
|
@ -62,7 +62,7 @@ export const getGateState = (gate: Gate): GateState => {
|
|||
id: gate.id,
|
||||
template: gate.template.metadata.name,
|
||||
transform: getTransformState(gate.transform),
|
||||
props: gate.props
|
||||
props: gate.getProps()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
47
src/modules/saving/templates/comment.ts
Normal file
47
src/modules/saving/templates/comment.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { PartialTemplate } from '../types/PartialTemplate'
|
||||
|
||||
/**
|
||||
* The template of the comment gate
|
||||
*/
|
||||
const commentTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'comment'
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 0
|
||||
},
|
||||
outputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
material: {
|
||||
fill: '#007A72'
|
||||
},
|
||||
shape: {
|
||||
scale: [300, 100]
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
context.innerText(context.getProperty('content'))
|
||||
`
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
|
||||
properties: {
|
||||
enabled: true,
|
||||
data: [
|
||||
{
|
||||
needsUpdate: true,
|
||||
base: 'Your comment here',
|
||||
name: 'content',
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
},
|
||||
innerText: {
|
||||
enabled: true,
|
||||
color: '#ADFFFA'
|
||||
}
|
||||
}
|
||||
|
||||
export default commentTemplate
|
32
src/modules/saving/templates/fullAdder.ts
Normal file
32
src/modules/saving/templates/fullAdder.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { PartialTemplate } from '../types/PartialTemplate'
|
||||
|
||||
/**
|
||||
* The template of the fullAdder gate
|
||||
*/
|
||||
const fullAdderTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'full adder'
|
||||
},
|
||||
material: {
|
||||
type: 'image',
|
||||
fill: require('../../../assets/full-adder')
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
const result = context.get(0) + context.get(1) + context.get(2)
|
||||
|
||||
context.set(0, result & 1)
|
||||
context.set(1, result >> 1)
|
||||
`
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 3
|
||||
},
|
||||
outputs: {
|
||||
count: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default fullAdderTemplate
|
32
src/modules/saving/templates/halfAdder.ts
Normal file
32
src/modules/saving/templates/halfAdder.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { PartialTemplate } from '../types/PartialTemplate'
|
||||
|
||||
/**
|
||||
* The template of the halfAdder gate
|
||||
*/
|
||||
const halfAdderTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'half adder'
|
||||
},
|
||||
material: {
|
||||
type: 'image',
|
||||
fill: require('../../../assets/half-adder')
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
const result = context.get(0) + context.get(1)
|
||||
|
||||
context.set(0, result & 1)
|
||||
context.set(1, result >> 1)
|
||||
`
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 2
|
||||
},
|
||||
outputs: {
|
||||
count: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default halfAdderTemplate
|
|
@ -1,4 +1,4 @@
|
|||
import { vector2 } from '../../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
|
||||
export type simulationMode = 'ic' | 'project'
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { GateTemplate, PinCount } from '../types/GateTemplate'
|
|||
import { idStore } from '../stores/idStore'
|
||||
import { Context, InitialisationContext } from '../../activation/types/Context'
|
||||
import { toFunction } from '../../activation/helpers/toFunction'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { Subscription, BehaviorSubject } from 'rxjs'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { getGateTimePipes } from '../helpers/getGateTimePipes'
|
||||
import { ImageStore } from '../../simulationRenderer/stores/imageStore'
|
||||
|
@ -15,6 +15,7 @@ import { saveStore } from '../../saving/stores/saveStore'
|
|||
import { Wire } from './Wire'
|
||||
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
|
||||
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
|
||||
import { tap } from 'rxjs/operators'
|
||||
|
||||
/**
|
||||
* The interface for the pins of a gate
|
||||
|
@ -115,10 +116,20 @@ export class Gate {
|
|||
*/
|
||||
public env: SimulationEnv = 'global'
|
||||
|
||||
/**
|
||||
* Holds all the gate-related text
|
||||
*/
|
||||
public text = {
|
||||
inner: new BehaviorSubject('text goes here')
|
||||
}
|
||||
|
||||
/**
|
||||
* The props used by the activation function (the same as memory but presists)
|
||||
*/
|
||||
public props: Record<string, string | number | boolean> = {}
|
||||
public props: Record<
|
||||
string,
|
||||
BehaviorSubject<string | number | boolean>
|
||||
> = {}
|
||||
|
||||
/**
|
||||
* The main logic gate class
|
||||
|
@ -129,7 +140,7 @@ export class Gate {
|
|||
public constructor(
|
||||
template: DeepPartial<GateTemplate> = {},
|
||||
id?: number,
|
||||
props: Record<string, string> = {}
|
||||
props: Record<string, string | number | boolean> = {}
|
||||
) {
|
||||
this.template = completeTemplate(template)
|
||||
|
||||
|
@ -259,15 +270,15 @@ export class Gate {
|
|||
/**
|
||||
* Assign the props passed to the gate and mere them with the base ones
|
||||
*/
|
||||
private assignProps(props: Record<string, string>) {
|
||||
private assignProps(props: Record<string, string | boolean | number>) {
|
||||
let shouldUpdate = false
|
||||
|
||||
if (this.template.properties.enabled) {
|
||||
for (const { base, name, needsUpdate } of this.template.properties
|
||||
.data) {
|
||||
this.props[name] = props.hasOwnProperty(name)
|
||||
? props[name]
|
||||
: base
|
||||
this.props[name] = new BehaviorSubject(
|
||||
props.hasOwnProperty(name) ? props[name] : base
|
||||
)
|
||||
|
||||
if (!shouldUpdate && needsUpdate) {
|
||||
shouldUpdate = true
|
||||
|
@ -301,6 +312,19 @@ export class Gate {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the props as an object
|
||||
*/
|
||||
public getProps() {
|
||||
const props: Record<string, string | boolean | number> = {}
|
||||
|
||||
for (const key in this.props) {
|
||||
props[key] = this.props[key].value
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears subscriptions to prevent memory leaks
|
||||
*/
|
||||
|
@ -349,10 +373,13 @@ export class Gate {
|
|||
}
|
||||
},
|
||||
getProperty: (name: string) => {
|
||||
return this.props[name]
|
||||
return this.props[name].value
|
||||
},
|
||||
setProperty: (name: string, value: string | number | boolean) => {
|
||||
this.props[name] = value
|
||||
this.props[name].next(value)
|
||||
},
|
||||
innerText: (value: string) => {
|
||||
this.text.inner.next(value)
|
||||
},
|
||||
update: () => {
|
||||
this.update()
|
||||
|
@ -369,7 +396,8 @@ export class Gate {
|
|||
* Generates pin wrappers from an array of pins
|
||||
*
|
||||
* @param pins The pins to wwap
|
||||
*/ private wrapPins(pins: Pin[]) {
|
||||
*/
|
||||
private wrapPins(pins: Pin[]) {
|
||||
const result: PinWrapper[] = []
|
||||
const length = pins.length
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { GateStorage } from './GateStorage'
|
|||
import { LruCacheNode } from '@eix-js/utils'
|
||||
import { Wire } from './Wire'
|
||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
|
||||
/**
|
||||
* The env a simulation can run in
|
||||
|
|
|
@ -53,5 +53,9 @@ export const DefaultGateTemplate: GateTemplate = {
|
|||
properties: {
|
||||
enabled: false,
|
||||
data: []
|
||||
},
|
||||
innerText: {
|
||||
enabled: false,
|
||||
color: 'white'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Gate } from '../classes/Gate'
|
|||
import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
|
||||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||
import { DefaultGateTemplate } from '../constants'
|
||||
import { vector2 } from '../../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
import { Screen } from '../../screen/helpers/Screen'
|
||||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { vector2 } from '../../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
|
||||
export interface PinCount {
|
||||
variable: boolean
|
||||
|
@ -75,4 +75,8 @@ export interface GateTemplate {
|
|||
enabled: boolean
|
||||
data: Property[]
|
||||
}
|
||||
innerText: {
|
||||
color: string
|
||||
enabled: boolean
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,42 @@
|
|||
import { Transform } from '../../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
import {
|
||||
substract,
|
||||
multiplyVectors,
|
||||
add,
|
||||
inverse
|
||||
} from '../../vector2/helpers/basic'
|
||||
|
||||
/**
|
||||
* worldPosition(x) = (x - p) / s
|
||||
* s * wp = x - p
|
||||
* screenPosition(x) = s * x + p
|
||||
*/
|
||||
|
||||
export class Camera {
|
||||
public transform = new Transform([0, 0])
|
||||
|
||||
/**
|
||||
* Gets the world position of a vector
|
||||
*
|
||||
* @param position The position to transform
|
||||
*/
|
||||
public toWordPostition(position: vector2) {
|
||||
return [
|
||||
(position[0] - this.transform.position[0]) /
|
||||
this.transform.scale[0],
|
||||
(position[1] - this.transform.position[1]) / this.transform.scale[1]
|
||||
] as vector2
|
||||
return multiplyVectors(
|
||||
substract(position, this.transform.position),
|
||||
inverse(this.transform.scale)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the screen position of a vector
|
||||
*
|
||||
* @param position The position to transform
|
||||
*/
|
||||
public toScreenPosition(position: vector2) {
|
||||
return add(
|
||||
multiplyVectors(position, this.transform.scale),
|
||||
this.transform.position
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import { Camera } from './Camera'
|
||||
import { Simulation } from '../../simulation/classes/Simulation'
|
||||
import { Subject } from 'rxjs'
|
||||
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
||||
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
||||
import { MouseEventInfo } from '../../core/types/MouseEventInfo'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
||||
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
||||
import {
|
||||
defaultSimulationRendererOptions,
|
||||
mouseButtons,
|
||||
shiftInput
|
||||
} from '../constants'
|
||||
import { getPinPosition } from '../helpers/pinPosition'
|
||||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||
import { defaultSimulationRendererOptions } from '../constants'
|
||||
import { SelectedPins } from '../types/SelectedPins'
|
||||
import { Wire } from '../../simulation/classes/Wire'
|
||||
import { currentStore } from '../../saving/stores/currentStore'
|
||||
import { saveStore } from '../../saving/stores/saveStore'
|
||||
import {
|
||||
|
@ -27,18 +18,14 @@ import { RefObject } from 'react'
|
|||
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
||||
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
||||
import { RendererState, WireState } from '../../saving/types/SimulationSave'
|
||||
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
|
||||
import { Transform } from '../../../common/math/classes/Transform'
|
||||
import { gatesInSelection } from '../helpers/gatesInSelection'
|
||||
import { selectionType } from '../types/selectionType'
|
||||
import { addIdToSelection, idIsSelected } from '../helpers/idIsSelected'
|
||||
import { GateInitter } from '../types/GateInitter'
|
||||
import {
|
||||
open as propsAreOpen,
|
||||
id as propsModalId
|
||||
} from '../../logic-gates/subjects/LogicGatePropsSubjects'
|
||||
import { handleMouseDown } from '../helpers/handleMouseDown'
|
||||
import { handleMouseUp } from '../helpers/handleMouseUp'
|
||||
import { handleMouseMove } from '../helpers/handleMouseMove'
|
||||
|
||||
export class SimulationRenderer {
|
||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||
|
@ -83,223 +70,9 @@ export class SimulationRenderer {
|
|||
}
|
||||
|
||||
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, template } = gates[index]
|
||||
|
||||
if (pointInSquare(worldPosition, transform)) {
|
||||
if (event.button === mouseButtons.drag) {
|
||||
gates[index].onClick()
|
||||
|
||||
this.mouseState |= 1
|
||||
|
||||
if (!idIsSelected(this, id)) {
|
||||
this.clearSelection()
|
||||
addIdToSelection(this, 'temporary', id)
|
||||
}
|
||||
|
||||
const gateNode = this.simulation.gates.get(id)
|
||||
|
||||
if (gateNode) {
|
||||
return this.simulation.gates.moveOnTop(gateNode)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Cannot find gate with id ${id}`
|
||||
)
|
||||
}
|
||||
} else if (
|
||||
event.button === mouseButtons.properties &&
|
||||
template.properties.enabled
|
||||
) {
|
||||
propsModalId.next(id)
|
||||
return propsAreOpen.next(true)
|
||||
}
|
||||
}
|
||||
|
||||
for (const pin of pins) {
|
||||
const position = getPinPosition(this, transform, pin)
|
||||
|
||||
if (
|
||||
pointInCircle(
|
||||
worldPosition,
|
||||
position,
|
||||
this.options.gates.pinRadius
|
||||
)
|
||||
) {
|
||||
if (pin.value.pairs.size) {
|
||||
if (pin.value.type & 1) {
|
||||
const wire = this.simulation.wires.find(
|
||||
wire => wire.end.value === pin.value
|
||||
)
|
||||
|
||||
if (wire) {
|
||||
deleteWire(this.simulation, wire)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Cannot find wire to remove`
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.selectedPins.start &&
|
||||
pin.value === this.selectedPins.start.wrapper.value
|
||||
) {
|
||||
this.selectedPins.start = null
|
||||
this.selectedPins.end = null
|
||||
} else if (
|
||||
this.selectedPins.end &&
|
||||
pin.value === this.selectedPins.end.wrapper.value
|
||||
) {
|
||||
this.selectedPins.start = null
|
||||
this.selectedPins.end = null
|
||||
} else if ((pin.value.type & 2) >> 1) {
|
||||
this.selectedPins.start = {
|
||||
wrapper: pin,
|
||||
transform
|
||||
}
|
||||
} else if (pin.value.type & 1) {
|
||||
this.selectedPins.end = {
|
||||
wrapper: pin,
|
||||
transform
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedPins.start && this.selectedPins.end) {
|
||||
this.simulation.wires.push(
|
||||
new Wire(
|
||||
this.selectedPins.start.wrapper,
|
||||
this.selectedPins.end.wrapper
|
||||
)
|
||||
)
|
||||
this.selectedPins.start = null
|
||||
this.selectedPins.end = null
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shiftInput.value && event.button === mouseButtons.unselect) {
|
||||
this.clearSelection()
|
||||
}
|
||||
|
||||
if (event.button === mouseButtons.pan) {
|
||||
// the second bit = pannning
|
||||
this.mouseState |= 0b10
|
||||
} else if (event.button === mouseButtons.select) {
|
||||
this.selectedArea.position = this.lastMousePosition
|
||||
this.selectedArea.scale = [0, 0]
|
||||
|
||||
// the third bit = selecting
|
||||
this.mouseState |= 0b100
|
||||
}
|
||||
})
|
||||
|
||||
this.mouseUpOutput.subscribe(event => {
|
||||
if (event.button === mouseButtons.drag && this.mouseState & 1) {
|
||||
const selected = this.getSelected()
|
||||
|
||||
for (const gate of selected) {
|
||||
gate.transform.rotation = 0
|
||||
}
|
||||
|
||||
this.selectedGates.temporary.clear()
|
||||
|
||||
// turn first bit to 0
|
||||
this.mouseState &= 6
|
||||
|
||||
// for debugging
|
||||
if ((this.mouseState >> 1) & 1 || this.mouseState & 1) {
|
||||
throw new SimulationError(
|
||||
'First 2 bits of mouseState need to be set to 0'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
event.button === mouseButtons.pan &&
|
||||
(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
|
||||
this.mouseState &= 3
|
||||
|
||||
const selectedGates = gatesInSelection(
|
||||
this.selectedArea,
|
||||
Array.from(this.simulation.gates)
|
||||
)
|
||||
|
||||
for (const { id } of selectedGates) {
|
||||
addIdToSelection(this, 'permanent', id)
|
||||
|
||||
const node = this.simulation.gates.get(id)
|
||||
|
||||
if (node) {
|
||||
this.simulation.gates.moveOnTop(node)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Cannot find node in gate storage with id ${id}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.mouseMoveOutput.subscribe(event => {
|
||||
const worldPosition = this.camera.toWordPostition(event.position)
|
||||
|
||||
const offset = invert(
|
||||
relativeTo(this.lastMousePosition, worldPosition)
|
||||
)
|
||||
|
||||
const scaledOffset = offset.map(
|
||||
(value, index) => value * this.camera.transform.scale[index]
|
||||
) as vector2
|
||||
|
||||
if (this.mouseState & 1) {
|
||||
for (const gate of this.getSelected()) {
|
||||
const { transform } = gate
|
||||
|
||||
transform.x -= offset[0]
|
||||
transform.y -= offset[1]
|
||||
}
|
||||
}
|
||||
|
||||
if ((this.mouseState >> 1) & 1) {
|
||||
this.camera.transform.position = add(
|
||||
this.camera.transform.position,
|
||||
invert(scaledOffset)
|
||||
)
|
||||
|
||||
this.spawnCount = 0
|
||||
}
|
||||
|
||||
if ((this.mouseState >> 2) & 1) {
|
||||
this.selectedArea.scale = relativeTo(
|
||||
this.selectedArea.position,
|
||||
this.camera.toWordPostition(event.position)
|
||||
)
|
||||
}
|
||||
|
||||
this.lastMousePosition = this.camera.toWordPostition(event.position)
|
||||
})
|
||||
this.mouseDownOutput.subscribe(handleMouseDown(this))
|
||||
this.mouseUpOutput.subscribe(handleMouseUp(this))
|
||||
this.mouseMoveOutput.subscribe(handleMouseMove(this))
|
||||
|
||||
this.reloadSave()
|
||||
}
|
||||
|
@ -316,6 +89,11 @@ export class SimulationRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a simulation state
|
||||
*
|
||||
* @param save LThe state to load
|
||||
*/
|
||||
public loadSave(save: RendererState) {
|
||||
this.simulation = fromSimulationState(save.simulation)
|
||||
this.camera = fromCameraState(save.camera)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
||||
import { vector2 } from '../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../common/math/types/vector2'
|
||||
import { mouseButton } from '../core/types/mouseButton'
|
||||
import { KeyboardInput } from '../keybindings/classes/KeyboardInput'
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { currentContext } from '../subjects'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
|
||||
/**
|
||||
* Gets the current context and throw an error if it doesnt exist
|
||||
*
|
||||
* @throws SimulationError if no context can be found
|
||||
*/
|
||||
export const getCurrentContextSafely = () => {
|
||||
if (currentContext.value) {
|
||||
return currentContext.value
|
||||
} else {
|
||||
throw new SimulationError(`Cannot find a rendering context`)
|
||||
}
|
||||
}
|
135
src/modules/simulationRenderer/helpers/handleMouseDown.ts
Normal file
135
src/modules/simulationRenderer/helpers/handleMouseDown.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
import { MouseEventInfo } from '../../core/types/MouseEventInfo'
|
||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
||||
import { idIsSelected, addIdToSelection } from './idIsSelected'
|
||||
import { mouseButtons, shiftInput } from '../constants'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { openGateProps } from './openGateProps'
|
||||
import { getPinPosition } from './pinPosition'
|
||||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
||||
import { Wire } from '../../simulation/classes/Wire'
|
||||
|
||||
export const handleMouseDown = (renderer: SimulationRenderer) => (
|
||||
event: MouseEventInfo
|
||||
) => {
|
||||
const worldPosition = renderer.camera.toWordPostition(event.position)
|
||||
const gates = Array.from(renderer.simulation.gates)
|
||||
|
||||
renderer.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, template } = gates[index]
|
||||
|
||||
if (pointInSquare(worldPosition, transform)) {
|
||||
if (event.button === mouseButtons.drag) {
|
||||
gates[index].onClick()
|
||||
|
||||
renderer.mouseState |= 1
|
||||
|
||||
if (!idIsSelected(renderer, id)) {
|
||||
renderer.clearSelection()
|
||||
addIdToSelection(renderer, 'temporary', id)
|
||||
}
|
||||
|
||||
const gateNode = renderer.simulation.gates.get(id)
|
||||
|
||||
if (gateNode) {
|
||||
return renderer.simulation.gates.moveOnTop(gateNode)
|
||||
} else {
|
||||
throw new SimulationError(`Cannot find gate with id ${id}`)
|
||||
}
|
||||
} else if (
|
||||
event.button === mouseButtons.properties &&
|
||||
template.properties.enabled
|
||||
) {
|
||||
return openGateProps(id)
|
||||
}
|
||||
}
|
||||
|
||||
for (const pin of pins) {
|
||||
const position = getPinPosition(renderer, transform, pin)
|
||||
|
||||
if (
|
||||
pointInCircle(
|
||||
worldPosition,
|
||||
position,
|
||||
renderer.options.gates.pinRadius
|
||||
)
|
||||
) {
|
||||
if (pin.value.pairs.size) {
|
||||
if (pin.value.type & 1) {
|
||||
const wire = renderer.simulation.wires.find(
|
||||
wire => wire.end.value === pin.value
|
||||
)
|
||||
|
||||
if (wire) {
|
||||
deleteWire(renderer.simulation, wire)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Cannot find wire to remove`
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
renderer.selectedPins.start &&
|
||||
pin.value === renderer.selectedPins.start.wrapper.value
|
||||
) {
|
||||
renderer.selectedPins.start = null
|
||||
renderer.selectedPins.end = null
|
||||
} else if (
|
||||
renderer.selectedPins.end &&
|
||||
pin.value === renderer.selectedPins.end.wrapper.value
|
||||
) {
|
||||
renderer.selectedPins.start = null
|
||||
renderer.selectedPins.end = null
|
||||
} else if ((pin.value.type & 2) >> 1) {
|
||||
renderer.selectedPins.start = {
|
||||
wrapper: pin,
|
||||
transform
|
||||
}
|
||||
} else if (pin.value.type & 1) {
|
||||
renderer.selectedPins.end = {
|
||||
wrapper: pin,
|
||||
transform
|
||||
}
|
||||
}
|
||||
|
||||
if (renderer.selectedPins.start && renderer.selectedPins.end) {
|
||||
renderer.simulation.wires.push(
|
||||
new Wire(
|
||||
renderer.selectedPins.start.wrapper,
|
||||
renderer.selectedPins.end.wrapper
|
||||
)
|
||||
)
|
||||
renderer.selectedPins.start = null
|
||||
renderer.selectedPins.end = null
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shiftInput.value && event.button === mouseButtons.unselect) {
|
||||
renderer.clearSelection()
|
||||
}
|
||||
|
||||
if (event.button === mouseButtons.pan) {
|
||||
// the second bit = pannning
|
||||
renderer.mouseState |= 0b10
|
||||
} else if (event.button === mouseButtons.select) {
|
||||
renderer.selectedArea.position = renderer.lastMousePosition
|
||||
renderer.selectedArea.scale = [0, 0]
|
||||
|
||||
// the third bit = selecting
|
||||
renderer.mouseState |= 0b100
|
||||
}
|
||||
}
|
47
src/modules/simulationRenderer/helpers/handleMouseMove.ts
Normal file
47
src/modules/simulationRenderer/helpers/handleMouseMove.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
import { MouseEventInfo } from '../../core/types/MouseEventInfo'
|
||||
import {
|
||||
substract,
|
||||
multiplyVectors,
|
||||
relativeTo
|
||||
} from '../../vector2/helpers/basic'
|
||||
|
||||
export const handleMouseMove = (renderer: SimulationRenderer) => (
|
||||
event: MouseEventInfo
|
||||
) => {
|
||||
const worldPosition = renderer.camera.toWordPostition(event.position)
|
||||
|
||||
const offset = substract(renderer.lastMousePosition, worldPosition)
|
||||
|
||||
const scaledOffset = multiplyVectors(
|
||||
offset,
|
||||
renderer.camera.transform.scale
|
||||
)
|
||||
|
||||
if (renderer.mouseState & 1) {
|
||||
for (const gate of renderer.getSelected()) {
|
||||
const { transform } = gate
|
||||
|
||||
transform.x -= offset[0]
|
||||
transform.y -= offset[1]
|
||||
}
|
||||
}
|
||||
|
||||
if ((renderer.mouseState >> 1) & 1) {
|
||||
renderer.camera.transform.position = substract(
|
||||
renderer.camera.transform.position,
|
||||
scaledOffset
|
||||
)
|
||||
|
||||
renderer.spawnCount = 0
|
||||
}
|
||||
|
||||
if ((renderer.mouseState >> 2) & 1) {
|
||||
renderer.selectedArea.scale = relativeTo(
|
||||
renderer.selectedArea.position,
|
||||
renderer.camera.toWordPostition(event.position)
|
||||
)
|
||||
}
|
||||
|
||||
renderer.lastMousePosition = renderer.camera.toWordPostition(event.position)
|
||||
}
|
59
src/modules/simulationRenderer/helpers/handleMouseUp.ts
Normal file
59
src/modules/simulationRenderer/helpers/handleMouseUp.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
import { MouseEventInfo } from '../../core/types/MouseEventInfo'
|
||||
import { mouseButtons } from '../constants'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { addIdToSelection } from './idIsSelected'
|
||||
import { gatesInSelection } from './gatesInSelection'
|
||||
|
||||
export const handleMouseUp = (renderer: SimulationRenderer) => (
|
||||
event: MouseEventInfo
|
||||
) => {
|
||||
if (event.button === mouseButtons.drag && renderer.mouseState & 1) {
|
||||
const selected = renderer.getSelected()
|
||||
|
||||
for (const gate of selected) {
|
||||
gate.transform.rotation = 0
|
||||
}
|
||||
|
||||
renderer.selectedGates.temporary.clear()
|
||||
|
||||
// turn first bit to 0
|
||||
renderer.mouseState &= 6
|
||||
|
||||
// for debugging
|
||||
if ((renderer.mouseState >> 1) & 1 || renderer.mouseState & 1) {
|
||||
throw new SimulationError(
|
||||
'First 2 bits of mouseState need to be set to 0'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (event.button === mouseButtons.pan && (renderer.mouseState >> 1) & 1) {
|
||||
// turn second bit to 0
|
||||
renderer.mouseState &= 5
|
||||
}
|
||||
|
||||
if (event.button === mouseButtons.select && renderer.mouseState >> 2) {
|
||||
// turn the third bit to 0
|
||||
renderer.mouseState &= 3
|
||||
|
||||
const selectedGates = gatesInSelection(
|
||||
renderer.selectedArea,
|
||||
Array.from(renderer.simulation.gates)
|
||||
)
|
||||
|
||||
for (const { id } of selectedGates) {
|
||||
addIdToSelection(renderer, 'permanent', id)
|
||||
|
||||
const node = renderer.simulation.gates.get(id)
|
||||
|
||||
if (node) {
|
||||
renderer.simulation.gates.moveOnTop(node)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Cannot find node in gate storage with id ${id}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
src/modules/simulationRenderer/helpers/openGateProps.ts
Normal file
14
src/modules/simulationRenderer/helpers/openGateProps.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
open as propsAreOpen,
|
||||
id as propsModalId
|
||||
} from '../../logic-gates/subjects/LogicGatePropsSubjects'
|
||||
|
||||
/**
|
||||
* Opens the properties modal for a gate
|
||||
*
|
||||
* @param id The id of the gate
|
||||
*/
|
||||
export const openGateProps = (id: number) => {
|
||||
propsModalId.next(id)
|
||||
return propsAreOpen.next(true)
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
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) => {
|
||||
const delta = light[index] - position
|
||||
|
||||
return light[index] - (delta + (point[2] * delta) / light[2])
|
||||
}) as vector2
|
|
@ -5,6 +5,7 @@ import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
|
|||
import { renderClickedPins } from './renderClickedPins'
|
||||
import { renderWires } from './renderWires'
|
||||
import { renderSelectedArea } from './renderSelectedArea'
|
||||
import { currentContext } from '../subjects'
|
||||
|
||||
export const renderSimulation = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
|
@ -12,6 +13,11 @@ export const renderSimulation = (
|
|||
) => {
|
||||
clearCanvas(ctx)
|
||||
|
||||
// push current context if it changed
|
||||
if (currentContext.value !== ctx) {
|
||||
currentContext.next(ctx)
|
||||
}
|
||||
|
||||
const transform = renderer.camera.transform
|
||||
|
||||
ctx.translate(...transform.position)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { clamp } from '../../simulation/helpers/clamp'
|
||||
import { Camera } from '../classes/Camera'
|
||||
import { vector2 } from '../../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
import { multiply, substract } from '../../vector2/helpers/basic'
|
||||
import { repeat } from '../../vector2/helpers/repeat'
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
|
||||
/**
|
||||
* Gets the stroke width for wires
|
||||
*/
|
||||
export const wireRadius = (renderer: SimulationRenderer) => {
|
||||
return (
|
||||
2 *
|
||||
|
|
8
src/modules/simulationRenderer/subjects.ts
Normal file
8
src/modules/simulationRenderer/subjects.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { BehaviorSubject } from 'rxjs'
|
||||
|
||||
/**
|
||||
* Behavior subject holding the curentCnvasRenderingContext2d
|
||||
*/
|
||||
export const currentContext = new BehaviorSubject<null | CanvasRenderingContext2D>(
|
||||
null
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
import { vector2 } from '../../../common/math/classes/Transform'
|
||||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
|
||||
/**
|
||||
* Used to init a gate at a certain position
|
||||
|
|
Loading…
Reference in a new issue