mirror of
https://git.sfja.dk/sfja/h6-logicirc.git
synced 2025-05-15 16:58:08 +01:00
add wiring
This commit is contained in:
parent
6ead8c97e2
commit
36b29bdcef
@ -48,6 +48,23 @@ export class CanvasRenderer implements Renderer {
|
||||
g.fill();
|
||||
}
|
||||
|
||||
strokeLine(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
color: string,
|
||||
lineWidth: number,
|
||||
): void {
|
||||
const { g } = this;
|
||||
g.strokeStyle = color;
|
||||
g.lineWidth = lineWidth;
|
||||
g.beginPath();
|
||||
g.moveTo(x0, y0);
|
||||
g.lineTo(x1, y1);
|
||||
g.stroke();
|
||||
}
|
||||
|
||||
putImage(
|
||||
data: RendererImage,
|
||||
x: number,
|
||||
|
146
src/geometry.ts
Normal file
146
src/geometry.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import { Renderer } from "./renderer.ts";
|
||||
|
||||
export class V2 {
|
||||
constructor(
|
||||
public x: number,
|
||||
public y: number,
|
||||
) {}
|
||||
|
||||
clone(): V2 {
|
||||
return new V2(this.x, this.y);
|
||||
}
|
||||
|
||||
add(other: V2): V2 {
|
||||
return new V2(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
|
||||
sub(other: V2): V2 {
|
||||
return new V2(this.x - other.x, this.y - other.y);
|
||||
}
|
||||
|
||||
mul(factor: number): V2 {
|
||||
return new V2(this.x * factor, this.y * factor);
|
||||
}
|
||||
|
||||
div(factor: number): V2 {
|
||||
return new V2(this.x / factor, this.y / factor);
|
||||
}
|
||||
|
||||
pow(factor: number): V2 {
|
||||
return new V2(this.x ** factor, this.y ** factor);
|
||||
}
|
||||
|
||||
sum(): number {
|
||||
return this.x + this.y;
|
||||
}
|
||||
|
||||
len(): number {
|
||||
return Math.sqrt(this.pow(2).sum());
|
||||
}
|
||||
|
||||
abs(): V2 {
|
||||
return new V2(Math.abs(this.x), Math.abs(this.y));
|
||||
}
|
||||
}
|
||||
|
||||
export interface Geometry {
|
||||
pointInside(thisPos: V2, pointPos: V2): boolean;
|
||||
collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean;
|
||||
render(r: Renderer, pos: V2, color: string): void;
|
||||
}
|
||||
|
||||
export class Rect implements Geometry {
|
||||
constructor(
|
||||
public width: number,
|
||||
public height: number,
|
||||
) {}
|
||||
|
||||
v2(): V2 {
|
||||
return new V2(this.width, this.height);
|
||||
}
|
||||
|
||||
pointInside(thisPos: V2, pointPos: V2): boolean {
|
||||
const { x, y } = thisPos;
|
||||
const { width: w, height: h } = this;
|
||||
const { x: ox, y: oy } = pointPos;
|
||||
return ox > x && ox < x + w && oy > y && oy < y + h;
|
||||
}
|
||||
|
||||
collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean {
|
||||
if (other instanceof Rect) {
|
||||
const { x, y } = thisPos;
|
||||
const { width: w, height: h } = this;
|
||||
const { x: ox, y: oy } = otherPos;
|
||||
const { width: ow, height: oh } = other;
|
||||
return ox + ow > x && ox < x + w && oy + oh > y && oy < y + h;
|
||||
}
|
||||
return other.collidesWith(otherPos, this, thisPos);
|
||||
}
|
||||
|
||||
render(r: Renderer, pos: V2, color: string): void {
|
||||
r.fillRect(pos.x, pos.y, this.width, this.height, color);
|
||||
}
|
||||
}
|
||||
|
||||
export class Circle implements Geometry {
|
||||
constructor(
|
||||
public radius: number,
|
||||
) {}
|
||||
|
||||
pointInside(thisPos: V2, pointPos: V2): boolean {
|
||||
return pointPos.sub(thisPos).len() < this.radius;
|
||||
}
|
||||
|
||||
collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean {
|
||||
if (other instanceof Circle) {
|
||||
return otherPos.sub(thisPos).len() < other.radius;
|
||||
}
|
||||
if (other instanceof Rect) {
|
||||
const circleDistance = thisPos.sub(otherPos).abs();
|
||||
const halfRect = other.v2().div(2);
|
||||
if (
|
||||
circleDistance.x >= thisPos.x + halfRect.x ||
|
||||
circleDistance.y >= thisPos.y + halfRect.y
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
circleDistance.x < halfRect.x || circleDistance.y < halfRect.y
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return circleDistance.add(halfRect).pow(2).sum() < this.radius ** 2;
|
||||
}
|
||||
return other.collidesWith(otherPos, other, thisPos);
|
||||
}
|
||||
|
||||
render(r: Renderer, pos: V2, color: string): void {
|
||||
r.fillCirc(pos.x, pos.y, this.radius, color);
|
||||
}
|
||||
}
|
||||
|
||||
export class Shape implements Geometry {
|
||||
constructor(
|
||||
public innerShapes: [V2, Geometry][],
|
||||
) {}
|
||||
|
||||
pointInside(thisPos: V2, pointPos: V2): boolean {
|
||||
return this.innerShapes
|
||||
.some(([pos, shape]) =>
|
||||
shape.pointInside(thisPos.add(pos), pointPos)
|
||||
);
|
||||
}
|
||||
|
||||
collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean {
|
||||
return this.innerShapes
|
||||
.some(([pos, shape]) =>
|
||||
other.collidesWith(thisPos.add(pos), shape, otherPos)
|
||||
);
|
||||
}
|
||||
|
||||
render(r: Renderer, pos: V2, color: string): void {
|
||||
for (const [shapePos, shape] of this.innerShapes) {
|
||||
shape.render(r, shapePos.add(pos), color);
|
||||
}
|
||||
}
|
||||
}
|
18
src/grid.ts
18
src/grid.ts
@ -191,6 +191,24 @@ class TransformingRenderer implements Renderer {
|
||||
const { r, t: { s, ox, oy } } = this;
|
||||
r.fillCirc(x * s + ox, y * s + oy, radius * s, color);
|
||||
}
|
||||
strokeLine(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
color: string,
|
||||
lineWidth: number,
|
||||
): void {
|
||||
const { r, t: { s, ox, oy } } = this;
|
||||
r.strokeLine(
|
||||
x0 * s + ox,
|
||||
y0 * s + oy,
|
||||
x1 * s + ox,
|
||||
y1 * s + oy,
|
||||
color,
|
||||
lineWidth * s,
|
||||
);
|
||||
}
|
||||
putImage(
|
||||
data: RendererImage,
|
||||
x: number,
|
||||
|
@ -44,6 +44,16 @@ export class Painter implements Renderer {
|
||||
fillCirc(x: number, y: number, radius: number, color: string): void {
|
||||
this.r.fillCirc(x, y, radius, color);
|
||||
}
|
||||
strokeLine(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
color: string,
|
||||
lineWidth: number,
|
||||
): void {
|
||||
this.r.strokeLine(x0, y0, x1, y1, color, lineWidth);
|
||||
}
|
||||
putImage(
|
||||
data: RendererImage,
|
||||
x: number,
|
||||
|
@ -10,8 +10,9 @@ export interface Renderer {
|
||||
get height(): N;
|
||||
clear(color: string): void;
|
||||
fillRect(x: N, y: N, w: N, h: N, color: string): void;
|
||||
strokeRect(x: N, y: N, w: N, h: N, color: string, lineWidth: number): void;
|
||||
strokeRect(x: N, y: N, w: N, h: N, color: string, lineWidth: N): void;
|
||||
fillCirc(x: N, y: N, radius: N, color: string): void;
|
||||
strokeLine(x0: N, y0: N, x1: N, y1: N, color: string, lineWidth: N): void;
|
||||
putImage(data: RendererImage, x: N, y: N): void;
|
||||
putImage(data: RendererImage, x: N, y: N, w: N, h: N): void;
|
||||
putImage(
|
||||
|
297
src/simulator.ts
297
src/simulator.ts
@ -1,3 +1,4 @@
|
||||
import { Circle, Geometry, Rect, V2 } from "./geometry.ts";
|
||||
import { Grid } from "./grid.ts";
|
||||
import { Mouse } from "./input.ts";
|
||||
import { Painter } from "./painter.ts";
|
||||
@ -23,6 +24,9 @@ export class Simulator {
|
||||
if (this.toolbar.hover() === "break") {
|
||||
break hover;
|
||||
}
|
||||
if (this.tooltip.hover() === "break") {
|
||||
break hover;
|
||||
}
|
||||
document.body.style.cursor = "default";
|
||||
}
|
||||
|
||||
@ -36,11 +40,10 @@ export class Simulator {
|
||||
}
|
||||
|
||||
class Toolbar {
|
||||
private tools: ComponentFactory[] = [
|
||||
private tools: Component[] = [
|
||||
new SwitchComponent(),
|
||||
new LedComponent(),
|
||||
];
|
||||
private previews: Component[] = [];
|
||||
|
||||
private lastWidth = 0;
|
||||
private lastHeight = 0;
|
||||
@ -51,8 +54,6 @@ class Toolbar {
|
||||
private mouse: Mouse,
|
||||
private tooltip: Tooltip,
|
||||
) {
|
||||
this.fillPreviews();
|
||||
|
||||
this.mouse.addOnPress(() => {
|
||||
const { x, y } = this.mouse;
|
||||
|
||||
@ -63,14 +64,14 @@ class Toolbar {
|
||||
return "bubble";
|
||||
}
|
||||
|
||||
for (const [i, component] of this.previews.entries()) {
|
||||
for (const [i, component] of this.tools.entries()) {
|
||||
if (
|
||||
x >= i * 128 + 96 - 48 &&
|
||||
y >= this.lastHeight - 100 - 48 &&
|
||||
x < i * 128 + 96 + component.width * 32 + 32 &&
|
||||
y < this.lastHeight - 100 + component.height * 32 + 32
|
||||
) {
|
||||
this.tooltip.select(this.tools[i].newInstance());
|
||||
this.tooltip.select(this.tools[i]);
|
||||
return "stop";
|
||||
}
|
||||
}
|
||||
@ -79,14 +80,9 @@ class Toolbar {
|
||||
});
|
||||
}
|
||||
|
||||
private fillPreviews(): void {
|
||||
// this.previews = this.tools.map((tool) => tool.newInstance());
|
||||
this.previews = this.tools as unknown as Component[];
|
||||
}
|
||||
|
||||
hover(): "continue" | "break" {
|
||||
const { x, y } = this.mouse;
|
||||
for (const [i, component] of this.previews.entries()) {
|
||||
for (const [i, component] of this.tools.entries()) {
|
||||
if (
|
||||
x >= i * 128 + 96 - 48 &&
|
||||
y >= this.lastHeight - 100 - 48 &&
|
||||
@ -107,7 +103,7 @@ class Toolbar {
|
||||
this.lastHeight = r.height;
|
||||
|
||||
r.fillRect(0, r.height - 200, r.width, 200, "#aaa");
|
||||
for (const [i, component] of this.previews.entries()) {
|
||||
for (const [i, component] of this.tools.entries()) {
|
||||
r.strokeRect(
|
||||
i * 128 + 96 - 48,
|
||||
r.height - 100 - 48,
|
||||
@ -135,31 +131,69 @@ class Tooltip {
|
||||
|
||||
private shouldHover = false;
|
||||
|
||||
private isWiring = false;
|
||||
private selectedOutputTerm?: ComponentWireTerm;
|
||||
private selectedInputTerm?: ComponentWireTerm;
|
||||
|
||||
constructor(
|
||||
private circuit: Circuit,
|
||||
public mouse: Mouse,
|
||||
) {
|
||||
this.mouse.addOnPress(() => {
|
||||
if (!this.selectedComponent) {
|
||||
return "bubble";
|
||||
if (this.selectedComponent) {
|
||||
const x = Math.floor(this.mouse.x / 32);
|
||||
const y = Math.floor(this.mouse.y / 32);
|
||||
this.circuit.tryPlace(this.selectedComponent.clone(), x, y);
|
||||
return "stop";
|
||||
} else if (!this.isWiring && this.selectedOutputTerm) {
|
||||
this.isWiring = true;
|
||||
return "stop";
|
||||
} else if (this.isWiring && this.selectedInputTerm) {
|
||||
if (!this.selectedOutputTerm) {
|
||||
throw new Error("invalid state");
|
||||
}
|
||||
this.circuit.wire(
|
||||
this.selectedOutputTerm,
|
||||
this.selectedInputTerm,
|
||||
);
|
||||
this.isWiring = false;
|
||||
this.selectedOutputTerm = undefined;
|
||||
this.selectedInputTerm = undefined;
|
||||
} else {
|
||||
this.isWiring = false;
|
||||
this.selectedOutputTerm = undefined;
|
||||
}
|
||||
|
||||
const x = Math.floor(this.mouse.x / 32);
|
||||
const y = Math.floor(this.mouse.y / 32);
|
||||
this.circuit.place(this.selectedComponent, x, y);
|
||||
return "stop";
|
||||
return "bubble";
|
||||
});
|
||||
}
|
||||
|
||||
hover(): "continue" | "break" {
|
||||
if (!this.selectedComponent) {
|
||||
return "continue";
|
||||
}
|
||||
const x = Math.floor(this.mouse.x / 32);
|
||||
const y = Math.floor(this.mouse.y / 32);
|
||||
if (!this.circuit.placeIsOccupied(x, y)) {
|
||||
this.shouldHover = true;
|
||||
return "break";
|
||||
if (this.selectedComponent) {
|
||||
const x = Math.floor(this.mouse.x / 32);
|
||||
const y = Math.floor(this.mouse.y / 32);
|
||||
|
||||
this.shouldHover = false;
|
||||
if (!this.circuit.placeIsOccupied(x, y)) {
|
||||
this.shouldHover = true;
|
||||
return "break";
|
||||
}
|
||||
} else if (!this.isWiring) {
|
||||
const term = this.circuit.hoveredOutputTerminal();
|
||||
this.selectedOutputTerm = undefined;
|
||||
if (term) {
|
||||
this.selectedOutputTerm = term;
|
||||
document.body.style.cursor = "pointer";
|
||||
return "break";
|
||||
}
|
||||
} else {
|
||||
const term = this.circuit.hoveredInputTerminal();
|
||||
this.selectedInputTerm = undefined;
|
||||
if (term) {
|
||||
this.selectedInputTerm = term;
|
||||
document.body.style.cursor = "pointer";
|
||||
return "break";
|
||||
}
|
||||
}
|
||||
|
||||
return "continue";
|
||||
@ -168,7 +202,43 @@ class Tooltip {
|
||||
render(r: Renderer): void {
|
||||
const x = Math.floor(this.mouse.x / 32);
|
||||
const y = Math.floor(this.mouse.y / 32);
|
||||
this.selectedComponent?.renderTransparent(r, x * 32 + 16, y * 32 + 16);
|
||||
if (this.selectedComponent) {
|
||||
if (this.shouldHover) {
|
||||
this.selectedComponent
|
||||
.renderTransparent(r, x * 32 + 16, y * 32 + 16);
|
||||
}
|
||||
} else if (!this.isWiring) {
|
||||
const output = this.selectedOutputTerm;
|
||||
if (output) {
|
||||
output.geometry.render(r, output.pos, "#777");
|
||||
}
|
||||
} else if (this.isWiring) {
|
||||
const output = this.selectedOutputTerm;
|
||||
if (!output) {
|
||||
throw new Error("invalid state");
|
||||
}
|
||||
const input = this.selectedInputTerm;
|
||||
if (input) {
|
||||
input.geometry.render(r, input.pos, "#777");
|
||||
r.strokeLine(
|
||||
output.pos.x,
|
||||
output.pos.y,
|
||||
input.pos.x,
|
||||
input.pos.y,
|
||||
"222",
|
||||
2,
|
||||
);
|
||||
} else {
|
||||
r.strokeLine(
|
||||
output.pos.x,
|
||||
output.pos.y,
|
||||
this.mouse.x,
|
||||
this.mouse.y,
|
||||
"222",
|
||||
2,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select(component: Component): void {
|
||||
@ -186,8 +256,27 @@ type PlacedComponent = {
|
||||
y: number;
|
||||
};
|
||||
|
||||
interface WireSource {
|
||||
get x(): number;
|
||||
get y(): number;
|
||||
high(): boolean;
|
||||
}
|
||||
|
||||
interface WireDest {
|
||||
get x(): number;
|
||||
get y(): number;
|
||||
setHigh(): void;
|
||||
setLow(): void;
|
||||
}
|
||||
|
||||
type Wire = {
|
||||
source: WireSource;
|
||||
dest: WireDest;
|
||||
};
|
||||
|
||||
class Circuit {
|
||||
private components: PlacedComponent[] = [];
|
||||
private wires: Wire[] = [];
|
||||
|
||||
constructor(
|
||||
private mouse: Mouse,
|
||||
@ -215,37 +304,101 @@ class Circuit {
|
||||
}
|
||||
|
||||
placeIsOccupied(x: number, y: number): boolean {
|
||||
return this.components.some((c) => c.x == x && c.y == y);
|
||||
return this.components.some((c) =>
|
||||
c.component.collidesWith(
|
||||
new V2(c.x, c.y),
|
||||
c.component,
|
||||
new V2(x, y),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
hover(): "continue" | "break" {
|
||||
return "continue";
|
||||
hoveredOutputTerminal(): ComponentWireTerm | null {
|
||||
for (const { x, y, component } of this.components) {
|
||||
const term = component.hoveredOutputTerminal(
|
||||
new V2(x, y).mul(32),
|
||||
new V2(this.mouse.x, this.mouse.y),
|
||||
);
|
||||
if (term !== null) {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
place(component: Component, x: number, y: number): void {
|
||||
hoveredInputTerminal(): ComponentWireTerm | null {
|
||||
for (const { x, y, component } of this.components) {
|
||||
const term = component.hoveredInputTerminal(
|
||||
new V2(x, y).mul(32),
|
||||
new V2(this.mouse.x, this.mouse.y),
|
||||
);
|
||||
if (term !== null) {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
tryPlace(component: Component, x: number, y: number): void {
|
||||
if (this.placeIsOccupied(x, y)) {
|
||||
return;
|
||||
}
|
||||
this.components.push({ component, x, y });
|
||||
}
|
||||
|
||||
wire(source: WireSource, dest: WireDest): void {
|
||||
this.wires.push({ source, dest });
|
||||
}
|
||||
|
||||
render(r: Renderer): void {
|
||||
for (const { source, dest } of this.wires) {
|
||||
r.strokeLine(source.x, source.y, dest.x, dest.y, "black", 3);
|
||||
}
|
||||
for (const { component, x, y } of this.components) {
|
||||
component.render(r, x * 32 + 16, y * 32 + 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ComponentWireTerm implements WireSource, WireDest {
|
||||
constructor(
|
||||
public pos: V2,
|
||||
public geometry: Geometry,
|
||||
public component: Component,
|
||||
) {}
|
||||
get x(): number {
|
||||
return this.pos.x;
|
||||
}
|
||||
get y(): number {
|
||||
return this.pos.y;
|
||||
}
|
||||
high(): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
setHigh(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
setLow(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
interface Component {
|
||||
get width(): number;
|
||||
get height(): number;
|
||||
|
||||
collidesWith(thisPos: V2, other: Component, otherPos: V2): boolean;
|
||||
collidesWithGeometry(thisPos: V2, other: Geometry, otherPos: V2): boolean;
|
||||
render(r: Renderer, x: number, y: number): void;
|
||||
renderTransparent(r: Renderer, x: number, y: number): void;
|
||||
click?(x: number, y: number): void;
|
||||
hoveredInputTerminal(pos: V2, mousePos: V2): ComponentWireTerm | null;
|
||||
hoveredOutputTerminal(pos: V2, mousePos: V2): ComponentWireTerm | null;
|
||||
|
||||
clone(): Component;
|
||||
}
|
||||
|
||||
interface ComponentFactory {
|
||||
newInstance(): Component;
|
||||
}
|
||||
|
||||
class SwitchComponent implements Component, ComponentFactory {
|
||||
class SwitchComponent implements Component {
|
||||
private graphicOn = new Painter(96, 64);
|
||||
private graphicOff = new Painter(96, 64);
|
||||
|
||||
@ -254,6 +407,8 @@ class SwitchComponent implements Component, ComponentFactory {
|
||||
public width = 1;
|
||||
public height = 1;
|
||||
|
||||
private geometry = new Rect(2, 1);
|
||||
|
||||
constructor() {
|
||||
this.graphicOff.fillRect(64, 30, 16, 4, "black");
|
||||
this.graphicOff.fillCirc(48 + 32, 32, 8, "black");
|
||||
@ -264,10 +419,18 @@ class SwitchComponent implements Component, ComponentFactory {
|
||||
this.graphicOn.fillCirc(48, 32, 16, "red");
|
||||
}
|
||||
|
||||
newInstance(): Component {
|
||||
clone(): Component {
|
||||
return new SwitchComponent();
|
||||
}
|
||||
|
||||
collidesWith(thisPos: V2, other: Component, otherPos: V2): boolean {
|
||||
return other.collidesWithGeometry(otherPos, this.geometry, thisPos);
|
||||
}
|
||||
|
||||
collidesWithGeometry(thisPos: V2, other: Geometry, otherPos: V2): boolean {
|
||||
return this.geometry.collidesWith(thisPos, other, otherPos);
|
||||
}
|
||||
|
||||
render(r: Renderer, x: number, y: number): void {
|
||||
const graphic = this.switchOn ? this.graphicOn : this.graphicOff;
|
||||
graphic.render(r, x - 48, y - 32);
|
||||
@ -287,9 +450,29 @@ class SwitchComponent implements Component, ComponentFactory {
|
||||
this.switchOn = !this.switchOn;
|
||||
}
|
||||
}
|
||||
|
||||
hoveredInputTerminal(
|
||||
_componentPos: V2,
|
||||
_mousePos: V2,
|
||||
): ComponentWireTerm | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
hoveredOutputTerminal(
|
||||
componentPos: V2,
|
||||
mousePos: V2,
|
||||
): ComponentWireTerm | null {
|
||||
const geometry = new Circle(8);
|
||||
const pos = new V2(48, 16).add(componentPos);
|
||||
|
||||
if (geometry.pointInside(pos, mousePos)) {
|
||||
return new ComponentWireTerm(pos, geometry, this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class LedComponent implements Component, ComponentFactory {
|
||||
class LedComponent implements Component {
|
||||
private graphicOn = new Painter(96, 64);
|
||||
private graphicOff = new Painter(96, 64);
|
||||
|
||||
@ -298,6 +481,8 @@ class LedComponent implements Component, ComponentFactory {
|
||||
public width = 1;
|
||||
public height = 1;
|
||||
|
||||
private geometry = new Rect(2, 1);
|
||||
|
||||
constructor() {
|
||||
this.graphicOff.fillRect(16, 30, 16, 4, "black");
|
||||
this.graphicOff.fillCirc(16, 32, 8, "black");
|
||||
@ -308,10 +493,22 @@ class LedComponent implements Component, ComponentFactory {
|
||||
this.graphicOn.fillCirc(48, 32, 16, "red");
|
||||
}
|
||||
|
||||
newInstance(): Component {
|
||||
clone(): Component {
|
||||
return new LedComponent();
|
||||
}
|
||||
|
||||
collidesWith(thisPos: V2, other: Component, otherPos: V2): boolean {
|
||||
return other.collidesWithGeometry(
|
||||
otherPos,
|
||||
this.geometry,
|
||||
thisPos.sub(new V2(0, 0)),
|
||||
);
|
||||
}
|
||||
|
||||
collidesWithGeometry(thisPos: V2, other: Geometry, otherPos: V2): boolean {
|
||||
return this.geometry.collidesWith(thisPos, other, otherPos);
|
||||
}
|
||||
|
||||
render(r: Renderer, x: number, y: number): void {
|
||||
const graphic = this.switchOn ? this.graphicOn : this.graphicOff;
|
||||
graphic.render(r, x - 48, y - 32);
|
||||
@ -321,4 +518,24 @@ class LedComponent implements Component, ComponentFactory {
|
||||
const graphic = this.switchOn ? this.graphicOn : this.graphicOff;
|
||||
graphic.render(r, x - 48, y - 32, 0.5);
|
||||
}
|
||||
|
||||
hoveredInputTerminal(
|
||||
componentPos: V2,
|
||||
mousePos: V2,
|
||||
): ComponentWireTerm | null {
|
||||
const geometry = new Circle(8);
|
||||
const pos = new V2(-16, 16).add(componentPos);
|
||||
|
||||
if (geometry.pointInside(pos, mousePos)) {
|
||||
return new ComponentWireTerm(pos, geometry, this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
hoveredOutputTerminal(
|
||||
_componentPos: V2,
|
||||
_mousePos: V2,
|
||||
): ComponentWireTerm | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user