-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy path03_shapes.ts
157 lines (132 loc) · 4.4 KB
/
03_shapes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// run `npm run shapes` to execute
/*
PROBLEMA: implementare un sistema per disegnare forme geometriche sul canvas.
*/
import { pipe } from 'fp-ts/function'
import { Monoid, concatAll } from 'fp-ts/Monoid'
// -------------------------------------------------------------------------------------
// model
// -------------------------------------------------------------------------------------
export interface Point {
readonly x: number
readonly y: number
}
/**
* Una forma è una funzione che dato un punto
* restituisce `true` se il punto appartiene alla forma e `false` altrimenti
*
* > Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.
* > - John Carmack
*/
export type Shape = (point: Point) => boolean
/*
FFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFF
FFFFFFFTTTTTTTTFFFFFFF
FFFFFFFTTTTTTTTFFFFFFF
FFFFFFFTTTTTTTTFFFFFFF
FFFFFFFTTTTTTTTFFFFFFF
FFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFF
▧▧▧▧▧▧▧▧
▧▧▧▧▧▧▧▧
▧▧▧▧▧▧▧▧
▧▧▧▧▧▧▧▧
*/
// -------------------------------------------------------------------------------------
// primitives
// -------------------------------------------------------------------------------------
/**
* Crea una forma che rappresenta un cerchio
*/
export const disk = (center: Point, radius: number): Shape => (point) =>
distance(point, center) <= radius
// distanza euclidea
const distance = (p1: Point, p2: Point) =>
Math.sqrt(
Math.pow(Math.abs(p1.x - p2.x), 2) + Math.pow(Math.abs(p1.y - p2.y), 2)
)
// pipe(disk({ x: 200, y: 200 }, 100), draw)
// -------------------------------------------------------------------------------------
// combinators
// -------------------------------------------------------------------------------------
/**
* Possiamo definire un primo combinatore che data una forma
* restituisce la sua forma complementare (il negativo)
*/
export const outside = (s: Shape): Shape => (point) => !s(point)
// pipe(disk({ x: 200, y: 200 }, 100), outside, draw)
// -------------------------------------------------------------------------------------
// instances
// -------------------------------------------------------------------------------------
/**
* Un monoide in cui `concat` rappresenta l'unione di due forme
*/
export const MonoidUnion: Monoid<Shape> = {
concat: (first, second) => (point) => first(point) || second(point),
empty: () => false
}
// pipe(
// MonoidUnion.concat(
// disk({ x: 150, y: 200 }, 100),
// disk({ x: 250, y: 200 }, 100)
// ),
// draw
// )
/**
* Un monoide in cui `concat` rappresenta l'intersezione di due forme
*/
const MonoidIntersection: Monoid<Shape> = {
concat: (first, second) => (point) => first(point) && second(point),
empty: () => true
}
// pipe(
// MonoidIntersection.concat(
// disk({ x: 150, y: 200 }, 100),
// disk({ x: 250, y: 200 }, 100)
// ),
// draw
// )
/**
* Usando il combinatore `outside` e `MonoidIntersection` possiamo
* creare una forma che rappresenta un anello
*/
export const ring = (
point: Point,
bigRadius: number,
smallRadius: number
): Shape =>
MonoidIntersection.concat(
disk(point, bigRadius),
outside(disk(point, smallRadius))
)
// pipe(ring({ x: 200, y: 200 }, 100, 50), draw)
export const mickeymouse: ReadonlyArray<Shape> = [
disk({ x: 200, y: 200 }, 100),
disk({ x: 130, y: 100 }, 60),
disk({ x: 280, y: 100 }, 60)
]
// pipe(concatAll(MonoidUnion)(mickeymouse), draw)
// -------------------------------------------------------------------------------------
// utils
// -------------------------------------------------------------------------------------
export function draw(shape: Shape): void {
const canvas: HTMLCanvasElement = document.getElementById('canvas') as any
const ctx: CanvasRenderingContext2D = canvas.getContext('2d') as any
const width = canvas.width
const height = canvas.height
const imagedata = ctx.createImageData(width, height)
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const point: Point = { x, y }
if (shape(point)) {
const pixelIndex = (point.y * width + point.x) * 4
imagedata.data[pixelIndex] = 0
imagedata.data[pixelIndex + 1] = 0
imagedata.data[pixelIndex + 2] = 0
imagedata.data[pixelIndex + 3] = 255
}
}
}
ctx.putImageData(imagedata, 0, 0)
}