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 { 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 {
|
export class Transform {
|
||||||
|
public positionSubject = new BehaviorSubject<vector2>([0, 0])
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public position: vector2 = [0, 0],
|
public _position: vector2 = [0, 0],
|
||||||
public scale: vector2 = [1, 1],
|
public scale: vector2 = [1, 1],
|
||||||
public rotation = 0
|
public rotation = 0
|
||||||
) {}
|
) {
|
||||||
|
this.updatePositionSubject()
|
||||||
|
}
|
||||||
|
|
||||||
public getBoundingBox() {
|
public getBoundingBox() {
|
||||||
const result = [...this.position, ...this.scale] as vector4
|
const result = [...this.position, ...this.scale] as vector4
|
||||||
|
@ -37,6 +42,29 @@ export class Transform {
|
||||||
return edges as [vector2, vector2][]
|
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 */
|
/** Short forms for random stuff */
|
||||||
|
|
||||||
get x() {
|
get x() {
|
||||||
|
@ -77,10 +105,14 @@ export class Transform {
|
||||||
|
|
||||||
set x(value: number) {
|
set x(value: number) {
|
||||||
this.position = [value, this.y]
|
this.position = [value, this.y]
|
||||||
|
|
||||||
|
this.updatePositionSubject()
|
||||||
}
|
}
|
||||||
|
|
||||||
set y(value: number) {
|
set y(value: number) {
|
||||||
this.position = [this.x, value]
|
this.position = [this.x, value]
|
||||||
|
|
||||||
|
this.updatePositionSubject()
|
||||||
}
|
}
|
||||||
|
|
||||||
set width(value: number) {
|
set width(value: number) {
|
||||||
|
@ -92,7 +124,6 @@ export class Transform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type vector2 = [number, number]
|
|
||||||
export type vector3 = [number, number, number]
|
export type vector3 = [number, number, number]
|
||||||
export type vector4 = [number, number, number, number]
|
export type vector4 = [number, number, number, number]
|
||||||
export type vector8 = [
|
export type vector8 = [
|
||||||
|
|
|
@ -9,6 +9,7 @@ export interface Context {
|
||||||
get: (index: number) => boolean
|
get: (index: number) => boolean
|
||||||
set: (index: number, state: boolean) => void
|
set: (index: number, state: boolean) => void
|
||||||
color: (color: string) => void
|
color: (color: string) => void
|
||||||
|
innerText: (value: string) => void
|
||||||
update: () => void
|
update: () => void
|
||||||
enviroment: SimulationEnv
|
enviroment: SimulationEnv
|
||||||
colors: Record<string, string>
|
colors: Record<string, string>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
|
import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { mouseButton } from '../types/mouseButton'
|
import { MouseEventInfo } from '../types/MouseEventInfo'
|
||||||
import { MouseEventInfo } from './MouseEventInfo'
|
|
||||||
import { width, height } from '../../screen/helpers/Screen'
|
import { width, height } from '../../screen/helpers/Screen'
|
||||||
|
import { getEventInfo } from '../helpers/getEventInfo'
|
||||||
|
|
||||||
export interface FluidCanvasProps {
|
export interface FluidCanvasProps {
|
||||||
mouseDownOuput: Subject<MouseEventInfo>
|
mouseDownOuput: Subject<MouseEventInfo>
|
||||||
|
@ -11,15 +11,6 @@ export interface FluidCanvasProps {
|
||||||
mouseMoveOutput: Subject<MouseEventInfo>
|
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>) => (
|
export const mouseEventHandler = (output: Subject<MouseEventInfo>) => (
|
||||||
e: MouseEvent<HTMLCanvasElement>
|
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 './mui-overrides.scss';
|
||||||
@import './toasts.scss';
|
@import './toasts.scss';
|
||||||
|
|
||||||
|
* {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
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
|
* The info about the mouse passed to mouse subjects
|
|
@ -1,4 +1,4 @@
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { MouseEventInfo } from '../components/MouseEventInfo'
|
import { MouseEventInfo } from './MouseEventInfo'
|
||||||
|
|
||||||
export type MouseSubject = Subject<MouseEventInfo>
|
export type MouseSubject = Subject<MouseEventInfo>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import './GateProperties.scss'
|
import './GateProperties.scss'
|
||||||
import React, { ChangeEvent } from 'react'
|
import React, { ChangeEvent, MouseEvent } from 'react'
|
||||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||||
import { Property } from '../../simulation/types/GateTemplate'
|
import { Property } from '../../simulation/types/GateTemplate'
|
||||||
import { BehaviorSubject } from 'rxjs'
|
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import Divider from '@material-ui/core/Divider'
|
import Divider from '@material-ui/core/Divider'
|
||||||
import TextField from '@material-ui/core/TextField'
|
import TextField from '@material-ui/core/TextField'
|
||||||
|
@ -12,8 +11,6 @@ import { Gate } from '../../simulation/classes/Gate'
|
||||||
|
|
||||||
export interface GatePropertyProps {
|
export interface GatePropertyProps {
|
||||||
raw: Property
|
raw: Property
|
||||||
name: string
|
|
||||||
output: BehaviorSubject<string | number | boolean>
|
|
||||||
gate: Gate
|
gate: Gate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +19,10 @@ export interface GatePropertyProps {
|
||||||
*
|
*
|
||||||
* @param param0 The props passed to the component
|
* @param param0 The props passed to the component
|
||||||
*/
|
*/
|
||||||
export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
|
||||||
const outputSnapshot = useObservable(() => output, '')
|
const { name } = raw
|
||||||
|
const prop = gate.props[name]
|
||||||
|
const outputSnapshot = useObservable(() => prop, '')
|
||||||
|
|
||||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}:`
|
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}:`
|
||||||
|
|
||||||
|
@ -37,10 +36,8 @@ export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
||||||
value = Number(target.value)
|
value = Number(target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
gate.props[name] = value
|
|
||||||
|
|
||||||
if (raw.type !== 'boolean') {
|
if (raw.type !== 'boolean') {
|
||||||
output.next(target.value)
|
prop.next(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raw.needsUpdate === true) {
|
if (raw.needsUpdate === true) {
|
||||||
|
@ -49,13 +46,16 @@ export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = <></>
|
let input = <></>
|
||||||
if (raw.type === 'number' || raw.type === 'text') {
|
|
||||||
|
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
|
||||||
input = (
|
input = (
|
||||||
<TextField
|
<TextField
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
label={displayableName}
|
label={displayableName}
|
||||||
value={outputSnapshot}
|
value={outputSnapshot}
|
||||||
type={raw.type}
|
type={raw.type}
|
||||||
|
multiline={raw.type === 'string'}
|
||||||
|
rowsMax={7}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (raw.type === 'boolean') {
|
} else if (raw.type === 'boolean') {
|
||||||
|
@ -64,7 +64,7 @@ export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
|
||||||
<span className="checkbox-label">{displayableName}</span>
|
<span className="checkbox-label">{displayableName}</span>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
output.next(!outputSnapshot)
|
prop.next(!outputSnapshot)
|
||||||
}}
|
}}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
checked={!!outputSnapshot}
|
checked={!!outputSnapshot}
|
||||||
|
@ -93,7 +93,6 @@ const GateProperties = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const gate = node.data
|
const gate = node.data
|
||||||
const gateProps = gate.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -113,20 +112,11 @@ const GateProperties = () => {
|
||||||
<Divider id="gate-props-divider" />
|
<Divider id="gate-props-divider" />
|
||||||
|
|
||||||
{gate.template.properties.data.map((raw, index) => {
|
{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 (
|
return (
|
||||||
<GatePropery
|
<GatePropery
|
||||||
output={output}
|
|
||||||
raw={raw}
|
raw={raw}
|
||||||
name={name}
|
|
||||||
gate={gate}
|
gate={gate}
|
||||||
key={`${index}-${id.value}-${output.value}`}
|
key={`${index}-${id.value}`}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { BehaviorSubject, Subject } from 'rxjs'
|
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)
|
export const id = new BehaviorSubject<number>(0)
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import { InputStore } from '../../input/stores/InputStore'
|
import { InputStore } from '../../input/stores/InputStore'
|
||||||
import { CreateSimulationStore } from '../../create-simulation/stores/CreateSimulationStore'
|
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 = () => {
|
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 sequentialDelayerTemplate from './templates/sequentialDelayer'
|
||||||
import xnorTemplate from './templates/xnor'
|
import xnorTemplate from './templates/xnor'
|
||||||
import xorTemplate from './templates/xor'
|
import xorTemplate from './templates/xor'
|
||||||
|
import halfAdderTemplate from './templates/halfAdder'
|
||||||
|
import fullAdderTemplate from './templates/fullAdder'
|
||||||
|
|
||||||
export const defaultSimulationName = 'default'
|
export const defaultSimulationName = 'default'
|
||||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
|
@ -26,7 +28,10 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
rgbLightTemplate,
|
rgbLightTemplate,
|
||||||
sequentialDelayerTemplate,
|
sequentialDelayerTemplate,
|
||||||
xnorTemplate,
|
xnorTemplate,
|
||||||
xorTemplate
|
xorTemplate,
|
||||||
|
halfAdderTemplate,
|
||||||
|
fullAdderTemplate
|
||||||
|
// commentTemplate
|
||||||
]
|
]
|
||||||
|
|
||||||
export const baseSave: RendererState = {
|
export const baseSave: RendererState = {
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const getGateState = (gate: Gate): GateState => {
|
||||||
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
|
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'
|
export type simulationMode = 'ic' | 'project'
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { GateTemplate, PinCount } from '../types/GateTemplate'
|
||||||
import { idStore } from '../stores/idStore'
|
import { idStore } from '../stores/idStore'
|
||||||
import { Context, InitialisationContext } from '../../activation/types/Context'
|
import { Context, InitialisationContext } from '../../activation/types/Context'
|
||||||
import { toFunction } from '../../activation/helpers/toFunction'
|
import { toFunction } from '../../activation/helpers/toFunction'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription, BehaviorSubject } from 'rxjs'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { getGateTimePipes } from '../helpers/getGateTimePipes'
|
import { getGateTimePipes } from '../helpers/getGateTimePipes'
|
||||||
import { ImageStore } from '../../simulationRenderer/stores/imageStore'
|
import { ImageStore } from '../../simulationRenderer/stores/imageStore'
|
||||||
|
@ -15,6 +15,7 @@ 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'
|
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
|
||||||
|
import { tap } from 'rxjs/operators'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface for the pins of a gate
|
* The interface for the pins of a gate
|
||||||
|
@ -115,10 +116,20 @@ export class Gate {
|
||||||
*/
|
*/
|
||||||
public env: SimulationEnv = 'global'
|
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)
|
* 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
|
* The main logic gate class
|
||||||
|
@ -129,7 +140,7 @@ export class Gate {
|
||||||
public constructor(
|
public constructor(
|
||||||
template: DeepPartial<GateTemplate> = {},
|
template: DeepPartial<GateTemplate> = {},
|
||||||
id?: number,
|
id?: number,
|
||||||
props: Record<string, string> = {}
|
props: Record<string, string | number | boolean> = {}
|
||||||
) {
|
) {
|
||||||
this.template = completeTemplate(template)
|
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
|
* 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
|
let shouldUpdate = false
|
||||||
|
|
||||||
if (this.template.properties.enabled) {
|
if (this.template.properties.enabled) {
|
||||||
for (const { base, name, needsUpdate } of this.template.properties
|
for (const { base, name, needsUpdate } of this.template.properties
|
||||||
.data) {
|
.data) {
|
||||||
this.props[name] = props.hasOwnProperty(name)
|
this.props[name] = new BehaviorSubject(
|
||||||
? props[name]
|
props.hasOwnProperty(name) ? props[name] : base
|
||||||
: base
|
)
|
||||||
|
|
||||||
if (!shouldUpdate && needsUpdate) {
|
if (!shouldUpdate && needsUpdate) {
|
||||||
shouldUpdate = true
|
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
|
* Clears subscriptions to prevent memory leaks
|
||||||
*/
|
*/
|
||||||
|
@ -349,10 +373,13 @@ export class Gate {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProperty: (name: string) => {
|
getProperty: (name: string) => {
|
||||||
return this.props[name]
|
return this.props[name].value
|
||||||
},
|
},
|
||||||
setProperty: (name: string, value: string | number | boolean) => {
|
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: () => {
|
update: () => {
|
||||||
this.update()
|
this.update()
|
||||||
|
@ -369,7 +396,8 @@ export class Gate {
|
||||||
* Generates pin wrappers from an array of pins
|
* Generates pin wrappers from an array of pins
|
||||||
*
|
*
|
||||||
* @param pins The pins to wwap
|
* @param pins The pins to wwap
|
||||||
*/ private wrapPins(pins: Pin[]) {
|
*/
|
||||||
|
private wrapPins(pins: Pin[]) {
|
||||||
const result: PinWrapper[] = []
|
const result: PinWrapper[] = []
|
||||||
const length = pins.length
|
const length = pins.length
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { GateStorage } from './GateStorage'
|
||||||
import { LruCacheNode } from '@eix-js/utils'
|
import { LruCacheNode } from '@eix-js/utils'
|
||||||
import { Wire } from './Wire'
|
import { Wire } from './Wire'
|
||||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The env a simulation can run in
|
* The env a simulation can run in
|
||||||
|
|
|
@ -53,5 +53,9 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
properties: {
|
properties: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
data: []
|
data: []
|
||||||
|
},
|
||||||
|
innerText: {
|
||||||
|
enabled: false,
|
||||||
|
color: 'white'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Gate } from '../classes/Gate'
|
||||||
import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
|
import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
|
||||||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
import { DefaultGateTemplate } from '../constants'
|
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 { Screen } from '../../screen/helpers/Screen'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
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 {
|
export interface PinCount {
|
||||||
variable: boolean
|
variable: boolean
|
||||||
|
@ -75,4 +75,8 @@ export interface GateTemplate {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
data: Property[]
|
data: Property[]
|
||||||
}
|
}
|
||||||
|
innerText: {
|
||||||
|
color: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,42 @@
|
||||||
import { Transform } from '../../../common/math/classes/Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
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 {
|
export class Camera {
|
||||||
public transform = new Transform([0, 0])
|
public transform = new Transform([0, 0])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the world position of a vector
|
||||||
|
*
|
||||||
|
* @param position The position to transform
|
||||||
|
*/
|
||||||
public toWordPostition(position: vector2) {
|
public toWordPostition(position: vector2) {
|
||||||
return [
|
return multiplyVectors(
|
||||||
(position[0] - this.transform.position[0]) /
|
substract(position, this.transform.position),
|
||||||
this.transform.scale[0],
|
inverse(this.transform.scale)
|
||||||
(position[1] - this.transform.position[1]) / this.transform.scale[1]
|
)
|
||||||
] as vector2
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 { Camera } from './Camera'
|
||||||
import { Simulation } from '../../simulation/classes/Simulation'
|
import { Simulation } from '../../simulation/classes/Simulation'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
import { MouseEventInfo } from '../../core/types/MouseEventInfo'
|
||||||
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
|
||||||
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
||||||
import {
|
import { defaultSimulationRendererOptions } from '../constants'
|
||||||
defaultSimulationRendererOptions,
|
|
||||||
mouseButtons,
|
|
||||||
shiftInput
|
|
||||||
} from '../constants'
|
|
||||||
import { getPinPosition } from '../helpers/pinPosition'
|
|
||||||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
|
||||||
import { SelectedPins } from '../types/SelectedPins'
|
import { SelectedPins } from '../types/SelectedPins'
|
||||||
import { Wire } from '../../simulation/classes/Wire'
|
|
||||||
import { currentStore } from '../../saving/stores/currentStore'
|
import { currentStore } from '../../saving/stores/currentStore'
|
||||||
import { saveStore } from '../../saving/stores/saveStore'
|
import { saveStore } from '../../saving/stores/saveStore'
|
||||||
import {
|
import {
|
||||||
|
@ -27,18 +18,14 @@ 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'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
|
||||||
import { RendererState, WireState } from '../../saving/types/SimulationSave'
|
import { RendererState, WireState } from '../../saving/types/SimulationSave'
|
||||||
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
|
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
|
||||||
import { Transform } from '../../../common/math/classes/Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
import { gatesInSelection } from '../helpers/gatesInSelection'
|
|
||||||
import { selectionType } from '../types/selectionType'
|
import { selectionType } from '../types/selectionType'
|
||||||
import { addIdToSelection, idIsSelected } from '../helpers/idIsSelected'
|
|
||||||
import { GateInitter } from '../types/GateInitter'
|
import { GateInitter } from '../types/GateInitter'
|
||||||
import {
|
import { handleMouseDown } from '../helpers/handleMouseDown'
|
||||||
open as propsAreOpen,
|
import { handleMouseUp } from '../helpers/handleMouseUp'
|
||||||
id as propsModalId
|
import { handleMouseMove } from '../helpers/handleMouseMove'
|
||||||
} from '../../logic-gates/subjects/LogicGatePropsSubjects'
|
|
||||||
|
|
||||||
export class SimulationRenderer {
|
export class SimulationRenderer {
|
||||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||||
|
@ -83,223 +70,9 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
this.mouseDownOutput.subscribe(event => {
|
this.mouseDownOutput.subscribe(handleMouseDown(this))
|
||||||
const worldPosition = this.camera.toWordPostition(event.position)
|
this.mouseUpOutput.subscribe(handleMouseUp(this))
|
||||||
const gates = Array.from(this.simulation.gates)
|
this.mouseMoveOutput.subscribe(handleMouseMove(this))
|
||||||
|
|
||||||
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.reloadSave()
|
this.reloadSave()
|
||||||
}
|
}
|
||||||
|
@ -316,6 +89,11 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a simulation state
|
||||||
|
*
|
||||||
|
* @param save LThe state to load
|
||||||
|
*/
|
||||||
public loadSave(save: RendererState) {
|
public loadSave(save: RendererState) {
|
||||||
this.simulation = fromSimulationState(save.simulation)
|
this.simulation = fromSimulationState(save.simulation)
|
||||||
this.camera = fromCameraState(save.camera)
|
this.camera = fromCameraState(save.camera)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
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 { mouseButton } from '../core/types/mouseButton'
|
||||||
import { KeyboardInput } from '../keybindings/classes/KeyboardInput'
|
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 { renderClickedPins } from './renderClickedPins'
|
||||||
import { renderWires } from './renderWires'
|
import { renderWires } from './renderWires'
|
||||||
import { renderSelectedArea } from './renderSelectedArea'
|
import { renderSelectedArea } from './renderSelectedArea'
|
||||||
|
import { currentContext } from '../subjects'
|
||||||
|
|
||||||
export const renderSimulation = (
|
export const renderSimulation = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -12,6 +13,11 @@ export const renderSimulation = (
|
||||||
) => {
|
) => {
|
||||||
clearCanvas(ctx)
|
clearCanvas(ctx)
|
||||||
|
|
||||||
|
// push current context if it changed
|
||||||
|
if (currentContext.value !== ctx) {
|
||||||
|
currentContext.next(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
const transform = renderer.camera.transform
|
const transform = renderer.camera.transform
|
||||||
|
|
||||||
ctx.translate(...transform.position)
|
ctx.translate(...transform.position)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/types/vector2'
|
||||||
import { multiply, substract } from '../../vector2/helpers/basic'
|
import { multiply, substract } from '../../vector2/helpers/basic'
|
||||||
import { repeat } from '../../vector2/helpers/repeat'
|
import { repeat } from '../../vector2/helpers/repeat'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stroke width for wires
|
||||||
|
*/
|
||||||
export const wireRadius = (renderer: SimulationRenderer) => {
|
export const wireRadius = (renderer: SimulationRenderer) => {
|
||||||
return (
|
return (
|
||||||
2 *
|
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
|
* Used to init a gate at a certain position
|
||||||
|
|
Loading…
Reference in a new issue