1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
9 [[15.2,32.1,7.6],[3,0.329,0.15,0.321,0.145,0.709,3,2,4,0.269,0.662],[0,"#000000",3,"#f5f5c1",12,"#158a34",68,"#89e681",100]],
10 [[15.2,32.1,5],[3,0.273,0.117,0.288,0.243,0.348,3,2,4,0.269,0.662],[1,"#000000",3,"#f5f5c1",8,"#158a34",17,"#89e681",20]],
11 [[4,12,1],[2,0.115,0.269,0.523,0.34,0.746,3,4,4,0.028,0.147],[0,"#36065e",0,"#c24242",77,"#8a19b0",91,"#ff9900",99,"#f5c816",99]],
12 [[4,12,1],[3,0.12,0.218,0.267,0.365,0.445,3,4,4,0.028,0.147],[0,"#000000",0,"#0f8a84",38,"#f5f5c1",43,"#158a34",70,"#89e681",100]],
13 [[4,12,1],[0,0.09,0.276,0.27,0.365,0.445,1,4,4,0.028,0.147],[0,"#93afd9",11,"#9cf0ff",92,"#edfdff",100]],
14 [[10.4,12,1],[2,0.082,0.302,0.481,0.35,0.749,2,3,4,0.028,0.147],[0,"#000000",11,"#ffffff",22,"#19a68a",85,"#6b0808",98]],
15 [[7.8,27.2,2.6],[3,0.21,0.714,0.056,0.175,0.838,2,0,2,0.132,0.311],[0,"#0a1340",0,"#ffffff",55,"#4da8a3",83,"#2652ab",99,"#2f1e75",46]],
16 [[4,12,1],[2,0.115,0.269,0.496,0.34,0.767,3,4,4,0.028,0.147],[0,"#b8cfcf",0,"#3f5a5c",77,"#1a330a",91,"#c0e0dc",99]],
17 [[10.6,31.8,1],[1,0.157,0.092,0.256,0.098,0.607,3,4,4,0.015,0.34],[0,"#4d3e3e",0,"#9a1ac9",77,"#aaf09e",100]],
18 [[3.2,34,14.8],[1,0.26,0.172,0.370,0.740,0.697,1,1,4,0.772,0.280],[0,"#3b8191",18,"#66f24f",82,"#698ffe",100]],
19 [[15.3,5.5,33.2],[1,0.746,0.283,0.586,0.702,0.148,1,2,0,0.379,0.633],[1,"#42ae80",77,"#fd1e2e",79,"#58103f",93,"#cf9750",96]],
20 [[2.5,3.5,7.7],[3,0.666,0.779,0.002,0.558,0.786,3,1,3,0.207,0.047],[0,"#a2898d",78,"#60d14e",86,"#5c4dea",90]],
21 [[7.6,7.6,9.0],[1,0.158,0.387,0.234,0.810,0.100,3,0,2,0.029,0.533],[0,"#568b8a",5,"#18ce42",92]]
24 var palettePresets
= [
25 [], // Placeholder for the palette of the currently selected preset.
26 [0, '#ffffff', 0, '#000000', 100],
27 [0, '#000000', 0, '#ffffff', 100],
28 [0, '#000000', 0, '#ff00ff', 50, '#000000', 100],
29 [0,"#000000",0,"#0f8a84",38,"#f5f5c1",43,"#158a34",70,"#89e681",100],
30 [1,"#000000",3,"#f5f5c1",8,"#158a34",17,"#89e681",20],
31 [0,"#36065e",0,"#c24242",77,"#8a19b0",91,"#ff9900",99,"#f5c816",99],
32 [0,"#93afd9",11,"#9cf0ff",92,"#edfdff",100],
33 [0,"#000000",11,"#ffffff",22,"#19a68a",85,"#6b0808",98],
34 [0,"#0a1340",0,"#ffffff",55,"#4da8a3",83,"#2652ab",99,"#2f1e75",46],
35 [0,"#b8cfcf",0,"#3f5a5c",77,"#1a330a",91,"#c0e0dc",99],
36 [0,"#4d3e3e",0,"#9a1ac9",77,"#aaf09e",100],
37 [1,"#52d2a1",3,"#c7c5ca",46,"#be6e88",72,"#f5a229",79,"#f0e0d1",94,"#6278d8",100]
41 * A helper function to abbreviate getElementById.
43 * @param {string} elementId The id to get.
46 function $(elementId
) {
47 return document
.getElementById(elementId
);
53 * @return {string} MIME type
55 function PNaClmimeType() {
56 return 'application/x-pnacl';
60 * Check if the browser supports PNaCl.
64 function browserSupportsPNaCl() {
65 var mimetype
= PNaClmimeType();
66 return navigator
.mimeTypes
[mimetype
] !== undefined;
70 * Get the URL for Google Cloud Storage.
72 * @param {string} name The relative path to the file.
75 function getDataURL(name
) {
76 var revision
= '236779';
77 var baseUrl
= '//storage.googleapis.com/gonacl/demos/publish/';
78 return baseUrl
+ revision
+ '/smoothlife/' + name
;
82 * Create the Native Client <embed> element as a child of the DOM element
85 * @param {string} name The name of the example.
86 * @param {number} width The width to create the plugin.
87 * @param {number} height The height to create the plugin.
88 * @param {Object} attrs Dictionary of attributes to set on the module.
90 function createNaClModule(name
, width
, height
, attrs
) {
91 var moduleEl
= document
.createElement('embed');
92 moduleEl
.setAttribute('name', 'nacl_module');
93 moduleEl
.setAttribute('id', 'nacl_module');
94 moduleEl
.setAttribute('width', width
);
95 moduleEl
.setAttribute('height', height
);
96 moduleEl
.setAttribute('path', '');
97 moduleEl
.setAttribute('src', getDataURL(name
+ '.nmf'));
98 moduleEl
.setAttribute('type', PNaClmimeType());
100 // Add any optional arguments
102 for (var key
in attrs
) {
103 moduleEl
.setAttribute(key
, attrs
[key
]);
107 // The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
108 // and a 'message' event listener attached. This wrapping method is used
109 // instead of attaching the event listeners directly to the <EMBED> element
110 // to ensure that the listeners are active before the NaCl module 'load'
112 var listenerDiv
= $('listener');
113 listenerDiv
.appendChild(moduleEl
);
117 * Add the default event listeners to the element with id "listener".
119 function attachDefaultListeners() {
120 var listenerDiv
= $('listener');
121 listenerDiv
.addEventListener('load', moduleDidLoad
, true);
122 listenerDiv
.addEventListener('error', moduleLoadError
, true);
123 listenerDiv
.addEventListener('progress', moduleLoadProgress
, true);
124 listenerDiv
.addEventListener('message', handleMessage
, true);
125 listenerDiv
.addEventListener('crash', handleCrash
, true);
130 * Called when the Browser can not communicate with the Module
132 * This event listener is registered in attachDefaultListeners above.
134 * @param {Object} event
136 function handleCrash(event
) {
137 if (naclModule
.exitStatus
== -1) {
138 updateStatus('CRASHED');
140 updateStatus('EXITED [' + naclModule
.exitStatus
+ ']');
145 * Called when the NaCl module is loaded.
147 * This event listener is registered in attachDefaultListeners above.
149 function moduleDidLoad() {
150 var bar
= $('progress-bar');
151 bar
.style
.width
= 100;
152 naclModule
= $('nacl_module');
161 * Hide the status field and progress bar.
163 function hideStatus() {
164 $('loading-cover').style
.display
= 'none';
168 * Called when the plugin fails to load.
170 * @param {Object} event
172 function moduleLoadError(event
) {
173 updateStatus('Load failed.');
177 * Called when the plugin reports progress events.
179 * @param {Object} event
181 function moduleLoadProgress(event
) {
182 $('progress').style
.display
= 'block';
184 var loadPercent
= 0.0;
185 var bar
= $('progress-bar');
187 if (event
.lengthComputable
&& event
.total
> 0) {
188 loadPercent
= event
.loaded
/ event
.total
* 100.0;
190 // The total length is not yet known.
193 bar
.style
.width
= loadPercent
+ "%";
197 * If the element with id 'statusField' exists, then set its HTML to the status
200 * @param {string} opt_message The message to set.
202 function updateStatus(opt_message
) {
203 var statusField
= $('statusField');
205 statusField
.style
.display
= 'block';
206 statusField
.textContent
= opt_message
;
211 * Add event listeners after the NaCl module has loaded. These listeners will
212 * forward messages to the NaCl module via postMessage()
214 function attachListeners() {
215 $('preset').addEventListener('change', loadSelectedPreset
);
216 $('palette').addEventListener('change', loadSelectedPalette
);
217 $('reset').addEventListener('click', loadSelectedPreset
);
218 $('clear').addEventListener('click', function() { clear(0); });
219 $('splat').addEventListener('click', function() { splat(); });
220 $('brushSizeRange').addEventListener('change', function() {
221 var radius
= parseFloat(this.value
);
222 setBrushSize(radius
, 1.0);
223 $('brushSize').textContent
= radius
.toFixed(1);
225 $('threadCount').addEventListener('change', function() {
226 setThreadCount(parseInt(this.value
, 10));
228 $('simSize').addEventListener('change', function() {
229 setSize(parseInt(this.value
, 10));
230 // changing the simulation size clears everything, so reset.
231 loadSelectedPreset();
233 $('scale').addEventListener('change', function() {
234 var scale
= parseFloat(this.value
);
239 setInterval(function() {
243 // Get the size of the embed and the size of the simulation, and
244 // determine the maximum scale.
245 var rect
= naclModule
.getBoundingClientRect();
246 var embedScale
= Math
.min(rect
.width
, rect
.height
);
247 var simSize
= parseInt($('simSize').value
, 10);
248 var maxScale
= embedScale
/ simSize
;
249 var scaleEl
= $('scale');
251 if (scaleEl
.max
!= maxScale
) {
252 var minScale
= scaleEl
.min
;
253 scaleEl
.disabled
= false;
254 var clampedScale
= Math
.min(maxScale
, Math
.max(minScale
, scaleEl
.value
));
256 scaleEl
.max
= maxScale
;
258 // Normally the minScale is 0.5, but sometimes maxScale can be less
259 // than that. In that case, set the minScale to maxScale.
260 scaleEl
.min
= Math
.min(maxScale
, 0.5);
262 // Reset the value so the input range updates.
264 scaleEl
.value
= clampedScale
;
267 // If max scale is too small, disable zoom.
268 scaleEl
.disabled
= maxScale
< minScale
;
272 function updateScaleText() {
273 var percent
= (parseFloat($('scale').value
) * 100).toFixed(0) + '%'
274 $('scaleValue').textContent
= percent
;
278 function loadSelectedPreset() {
279 loadPreset($('preset').value
);
282 function loadPreset(index
) {
283 var preset
= presets
[index
];
284 var selectedPalette
= $('palette').value
;
287 setKernel
.apply(null, preset
[0]);
288 setSmoother
.apply(null, preset
[1]);
289 // Only change the palette if it is set to "Default", which means to use
290 // the preset default palette.
291 if (selectedPalette
== 0)
292 setPalette
.apply(null, preset
[2]);
295 // Save the current palette in slot 0: "Default".
296 palettePresets
[0] = preset
[2];
299 function loadSelectedPalette() {
300 loadPalette($('palette').value
);
303 function loadPalette(index
) {
304 setPalette
.apply(null, palettePresets
[index
]);
307 function clear(color
) {
308 naclModule
.postMessage({cmd
: 'clear', color
: color
});
311 function setSize(size
) {
312 naclModule
.postMessage({cmd
: 'setSize', size
: size
});
315 function setMaxScale(scale
) {
316 naclModule
.postMessage({cmd
: 'setMaxScale', scale
: scale
});
319 function setThreadCount(threadCount
) {
320 naclModule
.postMessage({cmd
: 'setThreadCount', threadCount
: threadCount
});
324 function setKernel(discRadius
, ringRadius
, blendRadius
) {
325 naclModule
.postMessage({
327 discRadius
: discRadius
,
328 ringRadius
: ringRadius
,
329 blendRadius
: blendRadius
});
332 function setSmoother(type
, dt
, b1
, d1
, b2
, d2
, mode
, sigmoid
, mix
, sn
, sm
) {
333 naclModule
.postMessage({
336 b1
: b1
, d1
: d1
, b2
: b2
, d2
: d2
,
337 mode
: mode
, sigmoid
: sigmoid
, mix
: mix
,
341 function setPalette() {
342 var repeating
= arguments
[0] !== 0;
345 for (var i
= 1; i
< arguments
.length
; i
+= 2) {
346 colors
.push(arguments
[i
]);
347 stops
.push(arguments
[i
+ 1]);
349 naclModule
.postMessage({
351 repeating
: repeating
,
357 naclModule
.postMessage({cmd
: 'splat'});
360 function setBrushSize(radius
, color
) {
361 naclModule
.postMessage({cmd
: 'setBrush', radius
: radius
, color
: color
});
366 * Handle a message coming from the NaCl module.
367 * @param {Object} message_event
369 function handleMessage(message_event
) {
371 $('fps').textContent
= message_event
.data
.toFixed(1);
375 * Listen for the DOM content to be loaded. This event is fired when parsing of
376 * the page's document has finished.
378 document
.addEventListener('DOMContentLoaded', function() {
379 updateStatus('Loading...');
380 if (!browserSupportsPNaCl()) {
381 updateStatus('Browser does not support PNaCl or PNaCl is disabled');
382 } else if (naclModule
== null) {
383 createNaClModule('smoothnacl', '100%', '100%');
384 attachDefaultListeners();
386 // It's possible that the Native Client module onload event fired
387 // before the page's onload event. In this case, the status message
388 // will reflect 'SUCCESS', but won't be displayed. This call will
389 // display the current message.
390 updateStatus('Waiting.');