-
Notifications
You must be signed in to change notification settings - Fork 2
/
Main.hx
365 lines (312 loc) · 11.2 KB
/
Main.hx
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package;
import js.Browser;
import js.html.ButtonElement;
import js.html.DivElement;
import js.html.Element;
import js.html.Event;
import js.html.ParagraphElement;
import js.html.SelectElement;
import js.html.TextAreaElement;
import shape.Shape;
import util.CanvasRenderer;
import util.ShapeTweenOptimizer;
using StringTools;
// Automatic HTML code completion, you need to point these to your HTML
@:build(util.CodeCompletion.buildLocalFile("bin/index.html"))
//@:build(CodeCompletion.buildUrl("https://tweenoptimizer.geometrize.co.uk/"))
class ID {}
@:build(reader.FolderReader.build("bin/assets/cost_scripts"))
@:keep
class CostScripts {
}
@:build(reader.FolderReader.build("bin/assets/optimization_scripts"))
@:keep
class OptimizationScripts {
}
@:build(reader.FolderReader.build("bin/assets/preset_datasets"))
@:keep
class ShapeDatasets {
}
/**
* A shape tween optimization tool
* @author Sam Twidale (https://www.geometrize.co.uk/)
*/
class Main {
public static inline var demoUrl:String = "https://tweenoptimizer.geometrize.co.uk/"; // URL of the hosted website
private var score(get, set):Float;
private var passes(get, set):Int;
// References to the HTML page elements we need
private static inline function getElement(id:String):Dynamic {
return Browser.document.getElementById(id);
}
private static var scoreText:Element = getElement(ID.scoretext);
private static var optimizationPassesText:Element = getElement(ID.optimizationpassestext);
private static var rendererContainer:DivElement = getElement(ID.shapecanvascontainer);
private static var stepButton:ButtonElement = getElement(ID.stepbutton);
private static var resetButton:ButtonElement = getElement(ID.resetbutton);
private static var generalErrorsText:ParagraphElement = getElement(ID.generalerrors);
private static var costFunctionTextArea:TextAreaElement = getElement(ID.costfunctiontextedit);
private static var costFunctionPresetSelect:SelectElement = getElement(ID.costfunctionpresets);
private static var costFunctionErrorsText:ParagraphElement = getElement(ID.costfunctionerrors);
private static var optimizationFunctionTextArea:TextAreaElement = getElement(ID.optimizefunctiontextedit);
private static var optimizationFunctionPresetSelect:SelectElement = getElement(ID.optimizefunctionspresets);
private static var optimizationFunctionErrorsText:ParagraphElement = getElement(ID.optimizationfunctionerrors);
private static var datasetOnePresetSelect:SelectElement = getElement(ID.datasetonepresets);
private static var datasetOneTextArea:TextAreaElement = getElement(ID.datasetonetextedit);
private static var datasetOneErrorsText:ParagraphElement = getElement(ID.datasetoneerrors);
private static var datasetTwoPresetSelect:SelectElement = getElement(ID.datasettwopresets);
private static var datasetTwoTextArea:TextAreaElement = getElement(ID.datasettwotextedit);
private static var datasetTwoErrorsText:ParagraphElement = getElement(ID.datasettwoerrors);
private static var resultsDataTextArea:TextAreaElement = getElement(ID.resultsdatatextedit);
private var optimizer:ShapeTweenOptimizer;
private var renderer:CanvasRenderer;
private static function main():Void {
var main = new Main();
}
private inline function new() {
Browser.window.onload = onWindowLoaded;
}
private inline function onWindowLoaded():Void {
init();
}
/**
* One-time initialization
*/
private inline function init():Void {
setupEventListeners();
populateSelectMenus();
reset();
}
/**
* Reset everything to default settings
*/
private function reset():Void {
optimizer = new ShapeTweenOptimizer(this.render);
renderer = new CanvasRenderer(rendererContainer, 800, 800);
score = 0;
passes = 0;
resetErrors();
resetScripts();
resetDatasets();
step();
}
private function resetErrors():Void {
generalErrorsText.textContent = "";
costFunctionErrorsText.textContent = "";
optimizationFunctionErrorsText.textContent = "";
datasetOneErrorsText.textContent = "";
datasetTwoErrorsText.textContent = "";
}
private function resetScripts():Void {
costFunctionPresetSelect.value = "simpleweightings";
costFunctionPresetSelect.dispatchEvent(new Event("change"));
optimizationFunctionPresetSelect.value = "randomswapping";
optimizationFunctionPresetSelect.dispatchEvent(new Event("change"));
}
private function resetDatasets():Void {
datasetOnePresetSelect.value = "cinderella";
datasetOnePresetSelect.dispatchEvent(new Event("change"));
datasetTwoPresetSelect.value = "windflowers";
datasetTwoPresetSelect.dispatchEvent(new Event("change"));
}
/**
* Set up event listeners for page UI elements
*/
private inline function setupEventListeners():Void {
stepButton.addEventListener("click", function() {
step();
});
resetButton.addEventListener("click", function() {
reset();
});
costFunctionPresetSelect.addEventListener("change", function() {
var code = getPresetCostFunctionScript(costFunctionPresetSelect.value);
costFunctionTextArea.value = code;
setCostFunctionScript(code);
});
costFunctionTextArea.addEventListener("keypress", function() {
setCostFunctionScript(costFunctionTextArea.value);
});
costFunctionTextArea.addEventListener("change", function() {
setCostFunctionScript(costFunctionTextArea.value);
});
optimizationFunctionPresetSelect.addEventListener("change", function() {
var code = getPresetOptimizationFunctionScript(optimizationFunctionPresetSelect.value);
optimizationFunctionTextArea.value = code;
setOptimizationFunctionScript(code);
});
optimizationFunctionTextArea.addEventListener("keypress", function() {
setOptimizationFunctionScript(optimizationFunctionTextArea.value);
});
optimizationFunctionTextArea.addEventListener("change", function() {
setOptimizationFunctionScript(optimizationFunctionTextArea.value);
});
datasetOnePresetSelect.addEventListener("change", function() {
var data = getPresetShapeDataset(datasetOnePresetSelect.value);
datasetOneTextArea.value = data;
setDatasetOne(data);
});
datasetOneTextArea.addEventListener("keypress", function() {
setDatasetOne(datasetOneTextArea.value);
});
datasetOneTextArea.addEventListener("change", function() {
setDatasetOne(datasetOneTextArea.value);
});
datasetTwoPresetSelect.addEventListener("change", function() {
var data = getPresetShapeDataset(datasetTwoPresetSelect.value);
datasetTwoTextArea.value = data;
setDatasetTwo(data);
});
datasetTwoTextArea.addEventListener("keypress", function() {
setDatasetTwo(datasetTwoTextArea.value);
});
datasetTwoTextArea.addEventListener("change", function() {
setDatasetTwo(datasetTwoTextArea.value);
});
}
private function populateSelectMenus():Void {
var costScripts = Type.getClassFields(CostScripts);
var optimizeScripts = Type.getClassFields(OptimizationScripts);
var datasets = Type.getClassFields(ShapeDatasets);
var populateSelectMenu = function(fields:Array<String>, element:SelectElement):Void {
for (field in fields) {
var option = js.Browser.document.createOptionElement();
option.value = field;
option.appendChild(Browser.document.createTextNode(field));
element.appendChild(option);
}
}
populateSelectMenu(costScripts, costFunctionPresetSelect);
populateSelectMenu(optimizeScripts, optimizationFunctionPresetSelect);
populateSelectMenu(datasets, datasetOnePresetSelect);
populateSelectMenu(datasets, datasetTwoPresetSelect);
}
/**
* Step the shape tween optimizer
*/
private function step():Void {
try {
optimizer.optimize();
generalErrorsText.textContent = "";
score = optimizer.calculateTotalScore();
passes = passes + 1;
} catch (e:Dynamic) {
generalErrorsText.textContent = Std.string(e);
return;
}
render(optimizer.currentShapes);
updateResultsData();
}
/**
* Renders the shape data to the canvas
*/
public function render(shapes:Array<Shape>):Void {
renderer.render(shapes);
}
/**
* Sets the cost function used by the shape tween optimizer
* @param script hscript code for the cost function
*/
private function setCostFunctionScript(script:String):Void {
try {
optimizer.setCostScript(script);
costFunctionErrorsText.textContent = "";
} catch (e:Dynamic) {
costFunctionErrorsText.textContent = Std.string(e);
}
}
/**
* Sets the optimization function used by the shape tween optimizer
* @param script hscript code for the optimization function
*/
private function setOptimizationFunctionScript(script:String):Void {
try {
optimizer.setOptimizationScript(script);
optimizationFunctionErrorsText.textContent = "";
} catch (e:Dynamic) {
optimizationFunctionErrorsText.textContent = Std.string(e);
}
}
/**
* Updates the first set of shape data used by the shape tween optimizer
* @param json String containing JSON data describing the shapes
*/
private function setDatasetOne(json:String):Void {
try {
optimizer.setDatasetOne(json);
datasetOneErrorsText.textContent = "";
} catch (e:Dynamic) {
datasetOneErrorsText.textContent = Std.string(e);
}
}
/**
* Updates the second set of shape data used by the shape tween optimizer
* @param data String containing JSON data describing the shapes
*/
private function setDatasetTwo(json:String):Void {
try {
optimizer.setDatasetTwo(json);
datasetTwoErrorsText.textContent = "";
} catch (e:Dynamic) {
datasetTwoErrorsText.textContent = Std.string(e);
}
}
/**
* Updates the results data text area on the page
*/
private function updateResultsData():Void {
resultsDataTextArea.value = optimizer.getShapeIndicesMapping();
}
/**
* Gets the score value on the page
*/
private function get_score():Float {
return Std.parseFloat(scoreText.textContent);
}
/**
* Updates the score text value on the page
* @param score The tween optimization score value
*/
private function set_score(score:Float):Float {
scoreText.textContent = Std.string(Std.int(score));
return score;
}
/**
* Gets the number of passes text value on the page
*/
private function get_passes():Int {
return Std.parseInt(optimizationPassesText.textContent);
}
/**
* Updates the number of passes text value on the page
* @param passes The number of optimization passes
*/
private function set_passes(passes:Int):Int {
optimizationPassesText.textContent = Std.string(Std.int(passes));
return passes;
}
/**
* Gets the given cost function hscript code for the given id
* @param id The id (select element item) for the given preset
* @return Cost function code for the given script id
*/
private function getPresetCostFunctionScript(id:String):String {
return Reflect.field(CostScripts, id);
}
/**
* Gets the given optimization function hscript code for the given id
* @param id The id (select element item) for the given preset
* @return Optimization function code for the given script id
*/
private function getPresetOptimizationFunctionScript(id:String):String {
return Reflect.field(OptimizationScripts, id);
}
/**
* Gets the given preset shape dataset for the given id
* @param id The id (select element item) for the given preset
* @return Preset JSON code for the given shape dataset id
*/
private function getPresetShapeDataset(id:String):String {
return Reflect.field(ShapeDatasets, id);
}
}