Basic pin rendering
This commit is contained in:
parent
259786ad98
commit
7427a02842
|
@ -1,4 +1,4 @@
|
||||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
import { SimulationRenderer } from '../../../modules/simulationRenderer/classes/SimulationRenderer'
|
||||||
|
|
||||||
export const clearCanvas = (
|
export const clearCanvas = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
|
@ -1,4 +1,4 @@
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../math/types/vector2'
|
||||||
|
|
||||||
export const drawPolygon = (
|
export const drawPolygon = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
33
src/common/canvas/helpers/drawRotatedSquare.ts
Normal file
33
src/common/canvas/helpers/drawRotatedSquare.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { Transform } from '../../math/classes/Transform'
|
||||||
|
import { Material, Shape } from '../../../modules/simulation/types/GateTemplate'
|
||||||
|
import { roundRect } from './drawRoundedSquare'
|
||||||
|
|
||||||
|
export const drawRotatedSquare = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
{ position, scale, rotation }: Transform,
|
||||||
|
shape: Shape
|
||||||
|
) => {
|
||||||
|
ctx.save()
|
||||||
|
|
||||||
|
ctx.translate(...position)
|
||||||
|
ctx.translate(scale[0] / 2, scale[1] / 2)
|
||||||
|
|
||||||
|
ctx.rotate(rotation)
|
||||||
|
|
||||||
|
if (shape.rounded) {
|
||||||
|
roundRect(
|
||||||
|
ctx,
|
||||||
|
-scale[0] / 2,
|
||||||
|
-scale[1] / 2,
|
||||||
|
scale[0],
|
||||||
|
scale[1],
|
||||||
|
shape.radius
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.fill()
|
||||||
|
} else {
|
||||||
|
ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore()
|
||||||
|
}
|
23
src/common/canvas/helpers/drawRoundedSquare.ts
Normal file
23
src/common/canvas/helpers/drawRoundedSquare.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Credit: https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
|
||||||
|
*/
|
||||||
|
export function roundRect(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number = 5
|
||||||
|
) {
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(x + radius, y)
|
||||||
|
ctx.lineTo(x + width - radius, y)
|
||||||
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
|
||||||
|
ctx.lineTo(x + width, y + height - radius)
|
||||||
|
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
|
||||||
|
ctx.lineTo(x + radius, y + height)
|
||||||
|
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
|
||||||
|
ctx.lineTo(x, y + radius)
|
||||||
|
ctx.quadraticCurveTo(x, y, x + radius, y)
|
||||||
|
ctx.closePath()
|
||||||
|
}
|
14
src/common/canvas/helpers/useTransform.ts
Normal file
14
src/common/canvas/helpers/useTransform.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Transform } from '../../math/classes/Transform'
|
||||||
|
import { multiply } from '../../../modules/vector2/helpers/basic'
|
||||||
|
|
||||||
|
export const useTransform = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
{ position, rotation, scale }: Transform
|
||||||
|
) => {
|
||||||
|
ctx.translate(...position)
|
||||||
|
ctx.translate(scale[0] / 2, scale[1] / 2)
|
||||||
|
|
||||||
|
ctx.rotate(rotation)
|
||||||
|
|
||||||
|
return new Transform(multiply(scale, -0.5), scale, 0)
|
||||||
|
}
|
|
@ -1,19 +1,5 @@
|
||||||
import { allCombinations } from '../helpers/allCombinations'
|
import { allCombinations } from '../../../modules/simulation/helpers/allCombinations'
|
||||||
import { rotateAroundVector } from '../../vector2/helpers/rotate'
|
import { rotateAroundVector } from '../../../modules/vector2/helpers/rotate'
|
||||||
|
|
||||||
export type vector2 = [number, number]
|
|
||||||
export type vector3 = [number, number, number]
|
|
||||||
export type vector4 = [number, number, number, number]
|
|
||||||
export type vector8 = [
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number
|
|
||||||
]
|
|
||||||
|
|
||||||
export class Transform {
|
export class Transform {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
@ -106,3 +92,17 @@ export class Transform {
|
||||||
this.scale = [this.width, value]
|
this.scale = [this.width, value]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type vector2 = [number, number]
|
||||||
|
export type vector3 = [number, number, number]
|
||||||
|
export type vector4 = [number, number, number, number]
|
||||||
|
export type vector8 = [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number
|
||||||
|
]
|
10
src/common/math/helpers/pointInCircle.ts
Normal file
10
src/common/math/helpers/pointInCircle.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { vector2 } from '../types/vector2'
|
||||||
|
import { length, relativeTo } from '../../../modules/vector2/helpers/basic'
|
||||||
|
|
||||||
|
export const pointInCircle = (
|
||||||
|
point: vector2,
|
||||||
|
center: vector2,
|
||||||
|
radius: number
|
||||||
|
) => {
|
||||||
|
return length(relativeTo(point, center)) < radius
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { vector2, Transform } from '../../simulation/classes/Transform'
|
import { Transform } from '../classes/Transform'
|
||||||
|
import { vector2 } from '../types/vector2'
|
||||||
|
|
||||||
export const pointInSquare = (point: vector2, square: Transform) => {
|
export const pointInSquare = (point: vector2, square: Transform) => {
|
||||||
return (
|
return (
|
1
src/common/math/types/vector2.ts
Normal file
1
src/common/math/types/vector2.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type vector2 = [number, number]
|
1
src/common/math/types/vector3.ts
Normal file
1
src/common/math/types/vector3.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type vector3 = [number, number, number]
|
1
src/common/math/types/vector4.ts
Normal file
1
src/common/math/types/vector4.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type vector4 = [number, number, number, number]
|
10
src/common/math/types/vector8.ts
Normal file
10
src/common/math/types/vector8.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export type vector8 = [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number
|
||||||
|
]
|
|
@ -5,3 +5,7 @@ body {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
background-color: #222222;
|
||||||
|
}
|
||||||
|
|
|
@ -15,14 +15,22 @@ class Canvas extends Component {
|
||||||
public constructor(props: {}) {
|
public constructor(props: {}) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
const foo = new Gate('blue')
|
const foo = new Gate({
|
||||||
const bar = new Gate('green')
|
material: {
|
||||||
|
value: 'blue'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const bar = new Gate({
|
||||||
|
material: {
|
||||||
|
value: 'green'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
foo.transform.position = [100, 100]
|
foo.transform.position = [100, 100]
|
||||||
foo.transform.scale = [70, 70]
|
foo.transform.scale = [100, 100]
|
||||||
|
|
||||||
bar.transform.position = [200, 200]
|
bar.transform.position = [400, 200]
|
||||||
bar.transform.scale = [70, 70]
|
bar.transform.scale = [100, 100]
|
||||||
|
|
||||||
this.renderer.simulation.push(foo, bar)
|
this.renderer.simulation.push(foo, bar)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { RefObject, forwardRef, MouseEvent } from 'react'
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import { Screen } from '../classes/Screen'
|
import { Screen } from '../classes/Screen'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
|
||||||
const screen = new Screen()
|
const screen = new Screen()
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,64 @@
|
||||||
import { Transform } from './Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { Pin } from './Pin'
|
||||||
|
import merge from 'deepmerge'
|
||||||
|
import { GateTemplate, PinCount } from '../types/GateTemplate'
|
||||||
|
import { DefaultGateTemplate } from '../constants'
|
||||||
|
|
||||||
|
export interface GatePins {
|
||||||
|
inputs: Pin[]
|
||||||
|
outputs: Pin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PinWrapper {
|
||||||
|
total: number
|
||||||
|
index: number
|
||||||
|
value: Pin
|
||||||
|
}
|
||||||
|
|
||||||
export class Gate {
|
export class Gate {
|
||||||
public static lastId = 0
|
public static lastId = 0
|
||||||
|
|
||||||
public transform = new Transform()
|
public transform = new Transform()
|
||||||
public id = Gate.lastId++
|
public id = Gate.lastId++
|
||||||
|
public _pins: GatePins = {
|
||||||
|
inputs: [],
|
||||||
|
outputs: []
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(public color = 'blue') {}
|
public template: GateTemplate
|
||||||
|
|
||||||
|
public constructor(template: DeepPartial<GateTemplate> = {}) {
|
||||||
|
this.template = merge(DefaultGateTemplate, template) as GateTemplate
|
||||||
|
|
||||||
|
this._pins.inputs = Gate.generatePins(this.template.pins.inputs, 1)
|
||||||
|
this._pins.outputs = Gate.generatePins(this.template.pins.outputs, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrapPins(pins: Pin[]) {
|
||||||
|
const result: PinWrapper[] = []
|
||||||
|
const length = pins.length
|
||||||
|
|
||||||
|
for (let index = 0; index < length; index++) {
|
||||||
|
result.push({
|
||||||
|
index,
|
||||||
|
total: length,
|
||||||
|
value: pins[index]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pins() {
|
||||||
|
const result = [
|
||||||
|
...this.wrapPins(this._pins.inputs),
|
||||||
|
...this.wrapPins(this._pins.outputs)
|
||||||
|
]
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private static generatePins(options: PinCount, type: number) {
|
||||||
|
return [...Array(options.count)].fill(true).map(() => new Pin(type))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { SubscriptionData } from '../types/SubscriptionData'
|
import { SubscriptionData } from '../types/SubscriptionData'
|
||||||
import { BehaviorSubject } from 'rxjs'
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
|
||||||
|
/* Types:
|
||||||
|
|
||||||
|
First bit = input
|
||||||
|
Second bit = output
|
||||||
|
|
||||||
|
*/
|
||||||
export class Pin {
|
export class Pin {
|
||||||
public state = new BehaviorSubject(false)
|
public state = new BehaviorSubject(false)
|
||||||
public connectedTo = new Set<Pin>()
|
public connectedTo = new Set<Pin>()
|
||||||
|
@ -8,6 +14,8 @@ export class Pin {
|
||||||
private pairs = new Set<Pin>()
|
private pairs = new Set<Pin>()
|
||||||
private subscriptions: SubscriptionData<Pin>[] = []
|
private subscriptions: SubscriptionData<Pin>[] = []
|
||||||
|
|
||||||
|
public constructor(public type = 0b01) {}
|
||||||
|
|
||||||
public addPair(pin: Pin) {
|
public addPair(pin: Pin) {
|
||||||
this.pairs.add(pin)
|
this.pairs.add(pin)
|
||||||
|
|
||||||
|
|
22
src/modules/simulation/constants.ts
Normal file
22
src/modules/simulation/constants.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { GateTemplate } from './types/GateTemplate'
|
||||||
|
|
||||||
|
export const DefaultGateTemplate: GateTemplate = {
|
||||||
|
material: {
|
||||||
|
type: 'color',
|
||||||
|
value: 'blue'
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 2,
|
||||||
|
variable: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
count: 1,
|
||||||
|
variable: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
radius: 10,
|
||||||
|
rounded: true
|
||||||
|
}
|
||||||
|
}
|
23
src/modules/simulation/types/GateTemplate.ts
Normal file
23
src/modules/simulation/types/GateTemplate.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
export interface PinCount {
|
||||||
|
variable: boolean
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Material {
|
||||||
|
type: 'color'
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Shape {
|
||||||
|
rounded: boolean
|
||||||
|
radius: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GateTemplate {
|
||||||
|
material: Material
|
||||||
|
shape: Shape
|
||||||
|
pins: {
|
||||||
|
inputs: PinCount
|
||||||
|
outputs: PinCount
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { Transform, vector2 } from '../../simulation/classes/Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { Screen } from '../../core/classes/Screen'
|
import { Screen } from '../../core/classes/Screen'
|
||||||
import { relativeTo } from '../../vector2/helpers/basic'
|
import { relativeTo } from '../../vector2/helpers/basic'
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,17 @@ 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/FluidCanvas'
|
import { MouseEventInfo } from '../../core/components/FluidCanvas'
|
||||||
import { pointInSquare } from '../helpers/pointInSquare'
|
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { MouseVelocityManager } from './MouseVelocityManager'
|
import { MouseVelocityManager } from './MouseVelocityManager'
|
||||||
import { Screen } from '../../core/classes/Screen'
|
import { Screen } from '../../core/classes/Screen'
|
||||||
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
||||||
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
||||||
import { defaultSimulationRendererOptions } from '../constants'
|
import { defaultSimulationRendererOptions } from '../constants'
|
||||||
import merge from 'deepmerge'
|
import merge from 'deepmerge'
|
||||||
|
import { getPinPosition } from '../helpers/pinPosition'
|
||||||
|
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||||
|
import { SelectedPins } from '../types/SelectedPins'
|
||||||
|
|
||||||
export class SimulationRenderer {
|
export class SimulationRenderer {
|
||||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||||
|
@ -23,12 +26,18 @@ export class SimulationRenderer {
|
||||||
private selectedGate: number | null = null
|
private selectedGate: number | null = null
|
||||||
private gateSelectionOffset: vector2 = [0, 0]
|
private gateSelectionOffset: vector2 = [0, 0]
|
||||||
|
|
||||||
|
public lastMousePosition: vector2 = [0, 0]
|
||||||
public movedSelection = false
|
public movedSelection = false
|
||||||
public options: SimulationRendererOptions
|
public options: SimulationRendererOptions
|
||||||
public mouseManager = new MouseVelocityManager(this.mouseMoveOutput)
|
public mouseManager = new MouseVelocityManager(this.mouseMoveOutput)
|
||||||
public screen = new Screen()
|
public screen = new Screen()
|
||||||
public camera = new Camera()
|
public camera = new Camera()
|
||||||
|
|
||||||
|
public selectedPins: SelectedPins = {
|
||||||
|
start: null,
|
||||||
|
end: null
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
options: Partial<SimulationRendererOptions> = {},
|
options: Partial<SimulationRendererOptions> = {},
|
||||||
public simulation = new Simulation()
|
public simulation = new Simulation()
|
||||||
|
@ -43,11 +52,13 @@ export class SimulationRenderer {
|
||||||
const worldPosition = this.camera.toWordPostition(event.position)
|
const worldPosition = this.camera.toWordPostition(event.position)
|
||||||
const gates = Array.from(this.simulation.gates)
|
const gates = Array.from(this.simulation.gates)
|
||||||
|
|
||||||
|
this.lastMousePosition = worldPosition
|
||||||
|
|
||||||
// We need to iterate from the last to the first
|
// We need to iterate from the last to the first
|
||||||
// because if we have 2 overlapping gates,
|
// because if we have 2 overlapping gates,
|
||||||
// we want to select the one on top
|
// we want to select the one on top
|
||||||
for (let index = gates.length - 1; index >= 0; index--) {
|
for (let index = gates.length - 1; index >= 0; index--) {
|
||||||
const { transform, id } = gates[index]
|
const { transform, id, pins } = gates[index]
|
||||||
|
|
||||||
if (pointInSquare(worldPosition, transform)) {
|
if (pointInSquare(worldPosition, transform)) {
|
||||||
this.mouseManager.clear(worldPosition[0])
|
this.mouseManager.clear(worldPosition[0])
|
||||||
|
@ -69,9 +80,38 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const pin of pins) {
|
||||||
|
const position = getPinPosition(this, transform, pin)
|
||||||
|
|
||||||
|
if (
|
||||||
|
pointInCircle(
|
||||||
|
worldPosition,
|
||||||
|
position,
|
||||||
|
this.options.gates.pinRadius
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
(pin.value.type & 0b10) >> 1 &&
|
||||||
|
this.selectedPins.start === null
|
||||||
|
) {
|
||||||
|
this.selectedPins.start = {
|
||||||
|
wrapper: pin,
|
||||||
|
transform
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
pin.value.type & 1 &&
|
||||||
|
this.selectedPins.end === null
|
||||||
|
) {
|
||||||
|
this.selectedPins.end = {
|
||||||
|
wrapper: pin,
|
||||||
|
transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gateSelectionOffset = worldPosition
|
|
||||||
this.mouseState |= 2
|
this.mouseState |= 2
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -110,18 +150,16 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
if ((this.mouseState >> 1) & 1) {
|
if ((this.mouseState >> 1) & 1) {
|
||||||
const offset = invert(
|
const offset = invert(
|
||||||
relativeTo(this.gateSelectionOffset, worldPosition)
|
relativeTo(this.lastMousePosition, worldPosition)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.camera.transform.position = add(
|
this.camera.transform.position = add(
|
||||||
this.camera.transform.position,
|
this.camera.transform.position,
|
||||||
invert(offset)
|
invert(offset)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.gateSelectionOffset = this.camera.toWordPostition(
|
|
||||||
event.position
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lastMousePosition = this.camera.toWordPostition(event.position)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
||||||
|
|
||||||
export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
shadows: {
|
|
||||||
enabled: true,
|
|
||||||
color: 'rgba(0,0,0,0.3)',
|
|
||||||
gateHeight: 10,
|
|
||||||
lightHeight: 100
|
|
||||||
},
|
|
||||||
dnd: {
|
dnd: {
|
||||||
rotation: Math.PI / 12 // 7.5 degrees
|
rotation: Math.PI / 12 // 7.5 degrees
|
||||||
|
},
|
||||||
|
gates: {
|
||||||
|
connectionLength: 30,
|
||||||
|
pinRadius: 10,
|
||||||
|
pinStrokeColor: '#888888',
|
||||||
|
pinStrokeWidth: 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { Transform } from '../../simulation/classes/Transform'
|
|
||||||
|
|
||||||
export const drawRotatedSquare = (
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
{ position, scale, rotation }: Transform
|
|
||||||
) => {
|
|
||||||
ctx.save()
|
|
||||||
|
|
||||||
ctx.translate(...position)
|
|
||||||
ctx.translate(scale[0] / 2, scale[1] / 2)
|
|
||||||
|
|
||||||
ctx.rotate(rotation)
|
|
||||||
|
|
||||||
ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale)
|
|
||||||
|
|
||||||
ctx.restore()
|
|
||||||
}
|
|
15
src/modules/simulationRenderer/helpers/pinFill.ts
Normal file
15
src/modules/simulationRenderer/helpers/pinFill.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Pin } from '../../simulation/classes/Pin'
|
||||||
|
|
||||||
|
export const pinFill = (pin: Pin) => {
|
||||||
|
let color = 'rgba(0,0,0,0)'
|
||||||
|
|
||||||
|
if (pin.connectedTo.size) {
|
||||||
|
if (pin.state) {
|
||||||
|
color = 'yellow'
|
||||||
|
} else {
|
||||||
|
color = 'grey'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return color
|
||||||
|
}
|
66
src/modules/simulationRenderer/helpers/pinPosition.ts
Normal file
66
src/modules/simulationRenderer/helpers/pinPosition.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { Gate, PinWrapper } from '../../simulation/classes/Gate'
|
||||||
|
import { Pin } from '../../simulation/classes/Pin'
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
import { rotateAroundVector } from '../../vector2/helpers/rotate'
|
||||||
|
|
||||||
|
export const calculatePinY = (
|
||||||
|
transform: Transform,
|
||||||
|
index: number,
|
||||||
|
total: number
|
||||||
|
) => {
|
||||||
|
const space = transform.height / total
|
||||||
|
|
||||||
|
return (space * (2 * index + 1)) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export const calculatePinStart = (
|
||||||
|
transform: Transform,
|
||||||
|
type: number,
|
||||||
|
width: number
|
||||||
|
) => {
|
||||||
|
const direction = (type >> 1) & 1
|
||||||
|
const start =
|
||||||
|
transform.x + direction * transform.width - Number(!direction) * width
|
||||||
|
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
export const calculatePinx = (
|
||||||
|
start: number,
|
||||||
|
type: number,
|
||||||
|
connectionLength: number
|
||||||
|
) => {
|
||||||
|
return start + ((type >> 1) & 1) * connectionLength
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPinPosition = (
|
||||||
|
renderer: SimulationRenderer,
|
||||||
|
transform: Transform,
|
||||||
|
pin: PinWrapper
|
||||||
|
) => {
|
||||||
|
const { connectionLength } = renderer.options.gates
|
||||||
|
|
||||||
|
// render little connection
|
||||||
|
const start = calculatePinStart(
|
||||||
|
transform,
|
||||||
|
pin.value.type,
|
||||||
|
renderer.options.gates.connectionLength
|
||||||
|
)
|
||||||
|
|
||||||
|
const height = calculatePinY(transform, pin.index, pin.total)
|
||||||
|
|
||||||
|
const pinX = calculatePinx(start, pin.value.type, connectionLength)
|
||||||
|
const pinY = height + transform.y
|
||||||
|
|
||||||
|
// rotate
|
||||||
|
const notRotated: vector2 = [pinX, pinY]
|
||||||
|
const rotated = rotateAroundVector(
|
||||||
|
notRotated,
|
||||||
|
transform.center,
|
||||||
|
transform.rotation
|
||||||
|
)
|
||||||
|
|
||||||
|
return rotated
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { vector3, vector2 } from '../../simulation/classes/Transform'
|
import { vector3 } from '../../../common/math/types/vector3'
|
||||||
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
|
||||||
export const projectPointOnPlane = (point: vector3, light: vector3) =>
|
export const projectPointOnPlane = (point: vector3, light: vector3) =>
|
||||||
point.slice(0, 2).map((position, index) => {
|
point.slice(0, 2).map((position, index) => {
|
||||||
|
|
29
src/modules/simulationRenderer/helpers/renderClickedPins.ts
Normal file
29
src/modules/simulationRenderer/helpers/renderClickedPins.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
import { getPinPosition } from './pinPosition'
|
||||||
|
import { SelectedPin } from '../types/SelectedPins'
|
||||||
|
|
||||||
|
export const renderClickedPins = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
renderer: SimulationRenderer
|
||||||
|
) => {
|
||||||
|
let pin: SelectedPin | null = null
|
||||||
|
|
||||||
|
if (renderer.selectedPins.start) {
|
||||||
|
pin = renderer.selectedPins.start
|
||||||
|
} else if (renderer.selectedPins.end) {
|
||||||
|
pin = renderer.selectedPins.end
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin) {
|
||||||
|
const position = getPinPosition(renderer, pin.transform, pin.wrapper)
|
||||||
|
|
||||||
|
ctx.strokeStyle = 'yellow'
|
||||||
|
ctx.lineWidth = renderer.options.gates.pinRadius * 2
|
||||||
|
ctx.lineCap = 'round'
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(...position)
|
||||||
|
ctx.lineTo(...renderer.lastMousePosition)
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,17 @@
|
||||||
import { Gate } from '../../simulation/classes/Gate'
|
import { Gate } from '../../simulation/classes/Gate'
|
||||||
import { drawRotatedSquare } from './drawRotatedSquare'
|
import { drawRotatedSquare } from '../../../common/canvas/helpers/drawRotatedSquare'
|
||||||
|
import { renderPins } from './renderPins'
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
|
||||||
export const renderGate = (ctx: CanvasRenderingContext2D, gate: Gate) => {
|
export const renderGate = (
|
||||||
ctx.fillStyle = gate.color
|
ctx: CanvasRenderingContext2D,
|
||||||
drawRotatedSquare(ctx, gate.transform)
|
renderer: SimulationRenderer,
|
||||||
|
gate: Gate
|
||||||
|
) => {
|
||||||
|
renderPins(ctx, renderer, gate)
|
||||||
|
|
||||||
|
if (gate.template.material.type === 'color') {
|
||||||
|
ctx.fillStyle = gate.template.material.value
|
||||||
|
drawRotatedSquare(ctx, gate.transform, gate.template.shape)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { Gate } from '../../simulation/classes/Gate'
|
|
||||||
import { projectPointOnPlane } from './projectPoint'
|
|
||||||
import { drawPolygon } from './drawPolygon'
|
|
||||||
import { vector3 } from '../../simulation/classes/Transform'
|
|
||||||
|
|
||||||
export const renderGateShadow = (
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
color: string,
|
|
||||||
gate: Gate,
|
|
||||||
gateHeight: number,
|
|
||||||
light: vector3
|
|
||||||
) => {
|
|
||||||
ctx.fillStyle = color
|
|
||||||
|
|
||||||
const points = gate.transform.getPoints()
|
|
||||||
const projections = points.map(point =>
|
|
||||||
projectPointOnPlane([point[0], point[1], gateHeight], light)
|
|
||||||
)
|
|
||||||
|
|
||||||
drawPolygon(ctx, projections)
|
|
||||||
}
|
|
53
src/modules/simulationRenderer/helpers/renderPins.ts
Normal file
53
src/modules/simulationRenderer/helpers/renderPins.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { Gate } from '../../simulation/classes/Gate'
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
import { useTransform } from '../../../common/canvas/helpers/useTransform'
|
||||||
|
import { calculatePinStart, calculatePinY, calculatePinx } from './pinPosition'
|
||||||
|
import { pinFill } from './pinFill'
|
||||||
|
|
||||||
|
export const renderPins = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
renderer: SimulationRenderer,
|
||||||
|
gate: Gate
|
||||||
|
) => {
|
||||||
|
ctx.save()
|
||||||
|
|
||||||
|
const {
|
||||||
|
connectionLength,
|
||||||
|
pinRadius,
|
||||||
|
pinStrokeColor,
|
||||||
|
pinStrokeWidth
|
||||||
|
} = renderer.options.gates
|
||||||
|
const relativeTransform = useTransform(ctx, gate.transform)
|
||||||
|
|
||||||
|
ctx.strokeStyle = pinStrokeColor
|
||||||
|
ctx.lineWidth = pinStrokeWidth
|
||||||
|
|
||||||
|
for (const pin of gate.pins) {
|
||||||
|
ctx.fillStyle = pinFill(pin.value)
|
||||||
|
|
||||||
|
// render little connection
|
||||||
|
const start = calculatePinStart(
|
||||||
|
relativeTransform,
|
||||||
|
pin.value.type,
|
||||||
|
connectionLength
|
||||||
|
)
|
||||||
|
|
||||||
|
const height = calculatePinY(relativeTransform, pin.index, pin.total)
|
||||||
|
|
||||||
|
const pinX = calculatePinx(start, pin.value.type, connectionLength)
|
||||||
|
const pinY = height + relativeTransform.y
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(start, pinY)
|
||||||
|
ctx.lineTo(start + connectionLength, pinY)
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// render actual pin
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.ellipse(pinX, pinY, pinRadius, pinRadius, 0, 0, 2 * Math.PI)
|
||||||
|
ctx.fill()
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore()
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
import { relativeTo, invert } from '../../vector2/helpers/basic'
|
import { invert } from '../../vector2/helpers/basic'
|
||||||
import { renderGateShadow } from './renderGateShadow'
|
|
||||||
import { renderGate } from './renderGate'
|
import { renderGate } from './renderGate'
|
||||||
import { clearCanvas } from './clearCanvas'
|
import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
|
||||||
|
import { renderClickedPins } from './renderClickedPins'
|
||||||
|
|
||||||
export const renderSimulation = (
|
export const renderSimulation = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -12,25 +12,12 @@ export const renderSimulation = (
|
||||||
|
|
||||||
ctx.translate(...renderer.camera.transform.position)
|
ctx.translate(...renderer.camera.transform.position)
|
||||||
|
|
||||||
const center = relativeTo(
|
|
||||||
renderer.camera.transform.position,
|
|
||||||
renderer.screen.center
|
|
||||||
)
|
|
||||||
|
|
||||||
// render gates
|
// render gates
|
||||||
for (const gate of renderer.simulation.gates) {
|
for (const gate of renderer.simulation.gates) {
|
||||||
if (renderer.options.shadows.enabled) {
|
renderGate(ctx, renderer, gate)
|
||||||
renderGateShadow(
|
|
||||||
ctx,
|
|
||||||
renderer.options.shadows.color,
|
|
||||||
gate,
|
|
||||||
renderer.options.shadows.gateHeight,
|
|
||||||
[center[0], center[1], renderer.options.shadows.lightHeight]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGate(ctx, gate)
|
renderClickedPins(ctx, renderer)
|
||||||
}
|
|
||||||
|
|
||||||
ctx.translate(...invert(renderer.camera.transform.position))
|
ctx.translate(...invert(renderer.camera.transform.position))
|
||||||
}
|
}
|
||||||
|
|
7
src/modules/simulationRenderer/types/DeepPartial.ts
Normal file
7
src/modules/simulationRenderer/types/DeepPartial.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
type DeepPartial<T> = {
|
||||||
|
[P in keyof T]?: T[P] extends Array<infer U>
|
||||||
|
? Array<DeepPartial<U>>
|
||||||
|
: T[P] extends ReadonlyArray<infer U>
|
||||||
|
? ReadonlyArray<DeepPartial<U>>
|
||||||
|
: DeepPartial<T[P]>
|
||||||
|
}
|
12
src/modules/simulationRenderer/types/SelectedPins.ts
Normal file
12
src/modules/simulationRenderer/types/SelectedPins.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { PinWrapper } from '../../simulation/classes/Gate'
|
||||||
|
|
||||||
|
export interface SelectedPin {
|
||||||
|
wrapper: PinWrapper
|
||||||
|
transform: Transform
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectedPins {
|
||||||
|
start: SelectedPin | null
|
||||||
|
end: SelectedPin | null
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
export interface SimulationRendererOptions {
|
export interface SimulationRendererOptions {
|
||||||
shadows: {
|
|
||||||
enabled: boolean
|
|
||||||
color: string
|
|
||||||
lightHeight: number
|
|
||||||
gateHeight: number
|
|
||||||
}
|
|
||||||
dnd: {
|
dnd: {
|
||||||
rotation: number
|
rotation: number
|
||||||
}
|
}
|
||||||
|
gates: {
|
||||||
|
connectionLength: number
|
||||||
|
pinRadius: number
|
||||||
|
pinStrokeColor: string
|
||||||
|
pinStrokeWidth: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
|
||||||
// Basic stuff for arrays
|
// Basic stuff for arrays
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { length } from './basic'
|
import { length } from './basic'
|
||||||
|
|
||||||
export const minVector = (...vectors: vector2[]) => {
|
export const minVector = (...vectors: vector2[]) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { add, invert } from './basic'
|
import { add, invert } from './basic'
|
||||||
|
|
||||||
const { cos, sin } = Math
|
const { cos, sin } = Math
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { vector2 } from '../../simulation/classes/Transform'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
|
||||||
// TODO: rename
|
// TODO: rename
|
||||||
export const smoothStep = (step: number, current: vector2, target: vector2) => {
|
export const smoothStep = (step: number, current: vector2, target: vector2) => {
|
||||||
|
|
Loading…
Reference in a new issue