-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdynamic.js
122 lines (105 loc) · 3.5 KB
/
dynamic.js
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
"use strict";
// A way to change some parameters based on some other parameters.
//
// Options::
// max -- each result (in 0..1) is scaled to fit into this range
// min --
// type -- which parameter to use (velocity, direction, pressure, rotation, random)
// alpha -- exponential moving average coefficient. 0 = always use first value, 1 = no averaging.
//
class Dynamic
{
constructor(options)
{
this.type = 0;
this.min = 0;
this.max = 1;
this.alpha = 0.66;
for (let k in options)
this[k] = options[k];
}
// Lifecycle of a `Dynamic`:
//
// 1. When the user begins drawing: `reset(context)`
// 2. Before a single path leg is drawn: `start(context, tool, dx, dy, steps)`
// where `steps` is how many times `step` will be called.
// 3. While drawing a single path leg: `step(context)` should gradually change
// something to the desired value.
// 4. After a path leg is drawn: `stop(context)`.
// 5. When done drawing: `restore(context)`.
//
reset(ctx, tool)
{
let value = 0;
this._f = (current) => {
value = value || current;
return (value = value * (1 - this.alpha) + current * this.alpha);
};
}
start(ctx, tool, dx, dy, pressure, rotation)
{
let v = this.type == 1 ? Math.atan2(dy, dx) / 2 / Math.PI + 0.5
: this.type == 2 ? pressure
: this.type == 3 ? rotation
: this.type == 4 ? Math.random()
: Math.pow(dx * dx + dy * dy, 0.5) / 20;
return this.min + (this.max - this.min) * Math.min(1, Math.max(0, this._f(v)));
}
step(ctx, tool, total) {}
stop(ctx, tool) {}
restore(ctx, tool) {}
}
// A dynamic that changes some property of the canvas that has an associated
// tool option (e.g. `context.lineWidth` <=> `tool.options.size`).
//
// Options::
// source -- the option to use as an upper limit (see `Tool.options`)
// target -- the property of a 2d context to update with the result
// tgcopy -- the option of the tool to update with the result
//
class OptionDynamic extends Dynamic
{
reset(ctx, tool)
{
super.reset();
this._limit = this.source ? tool.options[this.source] : 1;
this._restore = this.tgcopy ? tool.options[this.tgcopy] : 0;
this._first = true;
}
start(ctx, tool, dx, dy, pressure, rotation)
{
this._value = super.start(ctx, tool, dx, dy, pressure, rotation) * this._limit;
if (this._first) {
this.stop(ctx, tool);
this._delta = 0;
this._first = false;
} else if (this.target) {
this._delta = this._value - ctx[this.target];
} else {
this._delta = this._value - tool.options[this.tgcopy];
}
}
step(ctx, tool, total)
{
if (this.tgcopy)
tool.options[this.tgcopy] += this._delta / total;
if (this.target)
ctx[this.target] += this._delta / total;
}
stop(ctx, tool)
{
if (this.tgcopy)
tool.options[this.tgcopy] = this._value;
if (this.target)
ctx[this.target] = this._value;
}
restore(ctx, tool)
{
if (this.tgcopy)
tool.options[this.tgcopy] = this._restore;
}
}
Dynamic.DEFAULT_SET = [
new OptionDynamic({'source': 'opacity', 'target': 'globalAlpha', 'type': 2}),
// new OptionDynamic({'source': 'size', 'target': 'lineWidth', 'type': 2}),
];