diff --git a/.classpath b/.classpath
index 342fdc9..d3e7ce3 100644
--- a/.classpath
+++ b/.classpath
@@ -7,29 +7,31 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..e7e9d11
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
diff --git a/.idea/libraries/libs.xml b/.idea/libraries/libs.xml
new file mode 100644
index 0000000..54ade39
--- /dev/null
+++ b/.idea/libraries/libs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/servlet_api.xml b/.idea/libraries/servlet_api.xml
new file mode 100644
index 0000000..06f8daa
--- /dev/null
+++ b/.idea/libraries/servlet_api.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e208459
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..feb3b82
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Knot-Renderer-Servlet.iml b/Knot-Renderer-Servlet.iml
new file mode 100644
index 0000000..0fa1651
--- /dev/null
+++ b/Knot-Renderer-Servlet.iml
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 56d511a..151e2d8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,36 @@
-# Knot-Renderer-Servlet
+# Visual-Curve-Servlet
+This code is a prototype for calculating the number of subdivisions required for isotopic convergence between a stick knot and its associated Bézier curve. More information on the topic can be [read about here](https://tpeters.engr.uconn.edu/JLPZ16-TIA-CTop.pdf).
+
+This prototype was succeeded by the [Subdivision Generator](https://github.uconn.edu/pdz10001/Subdivision-Generator). That code does not require a Web Server to run; you can run it locally where your hardware will determine the length of time to compute the subdivisions.
+
+For the time, the number of subdivisions required that is calculated lives on the server only. If you deploy the server, you'll see the result as M1, M2, and M3 in the logs. The max of M1, M2, and M3 is the upper bound on the number of subdivisions required.
+
+With the server up, load the webpage at http:localhost:8080/Knot-Renderer-Servlet. That will serve you the page where you can upload a curve file. An example curve file looks like the following:
+```
+1.307600000000000, -3.332000000000000, -2.507200000000000
+-1.384118500000000, 4.682550500000000, 0.913541000000000
+-3.2983075,-4.0566825,2.686189
+-0.1232995,2.768254,-2.463584
+3.9079915,-4.533357,1.2263705
+-3.935983,-0.438272,-0.983365
+3.218174000000000, 4.296123000000000, 2.112459500000000
+1.307600000000000, -3.332000000000000, -2.507200000000000
+```
+Those are points for a closed, simple stick knot; in this case it is the unknot. When those points are saved to a .curve file and uploaded, the associated Bézier curve is generated and displayed.
+
+If you press the "Toggle Stick Knot" button, it will show you the original stick knot from the file you uploaded.
+
+## Deploying your own server:
+To deploy your own server and calculate the required number of subdivisions you'll first need an instance of Apache Tomcat.
+
+1. Using Eclipse EE, right click -> Export -> WAR.
+
+2. Name the artifact ROOT.war
+
+3. Stop Tomcat
+
+4. Delete ROOT and ROOT.war in the Tomcat/webapps folder
+
+5. Copy over the new ROOT.war artifact
+
+6. Start the server again. You're good to go!
diff --git a/Testing_Files/quadratic-eighthed.curve b/Testing_Files/quadratic-eighthed.curve
new file mode 100644
index 0000000..c0558fd
--- /dev/null
+++ b/Testing_Files/quadratic-eighthed.curve
@@ -0,0 +1,25 @@
+0, 0, 0
+0, 0.625, 0
+0, 1.25, 0
+0, 1.875, 0
+0, 2.5, 0
+0, 3.125, 0
+0, 3.75, 0
+0, 4.375, 0
+0, 5, 0
+0.625, 5,0
+1.25, 5, 0
+1.875, 5, 0
+2.5, 5, 0
+3.125, 5, 0
+3.75, 5, 0
+4.375, 5, 0
+5, 5, 0
+5, 4.375, 0
+5,3.75, 0
+5,3.125, 0
+5,2.5, 0
+5, 1.875, 0
+5, 1.25, 0
+5, 0.625, 0
+5, 0, 0
diff --git a/WebContent/WEB-INF/lib/servlet-api.jar b/WebContent/WEB-INF/lib/servlet-api.jar
new file mode 100644
index 0000000..66ecbfb
Binary files /dev/null and b/WebContent/WEB-INF/lib/servlet-api.jar differ
diff --git a/WebContent/index.html b/WebContent/index.html
new file mode 100644
index 0000000..1a9c09c
--- /dev/null
+++ b/WebContent/index.html
@@ -0,0 +1,112 @@
+
+
+
+
+ Visual Curve Analyze
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to Visual Curve Analyze
+
+
+
+
+
+
+
+ To use this site press the browse button to select a file that contains a Stick knot (this is a list of 3D points; X, Y, Z in that order and each point on their own line). Then press upload to see the Bézier curve generated from the Stick knot you uploaded. Please make sure your file has a file extension of '.curve'. The more points you have the longer it will take to visualize.
+
+
+
+ Toggle Stick Knot
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebContent/knotviewer/knotviewer.pde b/WebContent/knotviewer/knotviewer.pde
new file mode 100644
index 0000000..a306e3b
--- /dev/null
+++ b/WebContent/knotviewer/knotviewer.pde
@@ -0,0 +1,230 @@
+// private static final float CAMERA_START_Z = 40f;
+// private static final float ZOOM_MULTIPLIER = 20.f;
+ private static final float CAMERA_START_Z = 40f;
+ private static final float ZOOM_MULTIPLIER = 5.f;
+
+ private static int WIDTH = 720;
+ private static int HEIGHT = 480;
+
+ //private RenderCurve renderCurve;
+ private float cameraX = WIDTH / 2;
+ private float cameraY = HEIGHT / 2;
+ private float cameraZ = CAMERA_START_Z;
+
+ private float startMouseX;
+ private float startMouseY;
+ private float rotationX = 0;
+ private float rotationY = 0;
+
+ private ArrayList bezierDrawPoints;
+ private ArrayList stickKnotPoints;
+
+ private boolean stickKnotDrawEnabled = false;
+
+ void setup()
+ {
+ size(720, 480, OPENGL);
+ background(100);
+ stroke(255);
+ ellipse(50, 50, 25, 25);
+ bezierDrawPoints = new ArrayList();
+ stickKnotPoints = new ArrayList();
+ //renderCurve = new RenderCurve(this, curve);
+ }
+
+ void processJSONCurve(JSONObject curvePoints)
+ {
+ JSONArray pointsArray = curvePoints.getJSONArray("points");
+ processJSONCurve(pointsArray);
+ }
+
+ void processJSONCurve(JSONArray curvePoints)
+ {
+ for(int i = 0; i < curvePoints.size(); i++)
+ {
+ JSONObject vector = curvePoints.getJSONObject(i);
+ double x = vector.getDouble("x");
+ double y = vector.getDouble("y");
+ double z = vector.getDouble("z");
+ bezierDrawPoints.add(new PVector((float)x, (float)y, (float)z));
+ }
+ }
+
+ void addBezierPoint(double x, double y, double z)
+ {
+ bezierDrawPoints.add(new PVector((float)x, (float)y, (float)z));
+ }
+
+ void addStickKnotPoint(double x, double y, double z)
+ {
+ stickKnotPoints.add(new PVector((float)x, (float)y, (float)z));
+ }
+
+ void mouseWheel(MouseEvent event)
+ {
+ cameraZ -= ZOOM_MULTIPLIER * event.getCount();
+ }
+
+ void frameResized(int w, int h) {
+ super.frameResized(w, h);
+ WIDTH = w;
+ System.out.println(WIDTH);
+ HEIGHT = h;
+ }
+
+ void keyPressed(KeyEvent event) {
+ super.keyPressed(event);
+ if(event.getKey() == CODED)
+ {
+ int keyCode = (int)event.getKeyCode();
+ switch (keyCode)
+ {
+ case UP:
+ rotationX += Math.PI / 6;
+ break;
+ case DOWN:
+ rotationX -= Math.PI / 6;
+ break;
+ case LEFT:
+ rotationY -= Math.PI / 6;
+ break;
+ case RIGHT:
+ rotationY += Math.PI / 6;
+ break;
+ }
+ }
+ else
+ {
+ switch(event.getKey())
+ {
+ case ' ':
+ //PDZ- when the user presses space make sure that the WIDTH and HEIGHT are updated to the latest
+ WIDTH = width;
+ HEIGHT = height;
+
+ //TODO: PDZ- Doing it this way will apply the same transformation to every curve. Might want to make it so that you
+ //can control/rotate/translate each curve individually **SEE BELOW**
+ cameraX = WIDTH/2;
+ cameraY = HEIGHT/2;
+
+ //PDZ- Uncomment these if you want the space bar to also undo any rotations and scaling of the knot/curve
+// cameraZ = CAMERA_START_Z;
+// rotationX = 0;
+// rotationY = 0;
+ break;
+ case 'w':
+ cameraY -= 20;
+ break;
+ case 's':
+ cameraY += 20;
+ break;
+ case 'a':
+ cameraX -=20;
+ break;
+ case 'd':
+ cameraX +=20;
+ break;
+ }
+
+ }
+ }
+
+ void mouseDragged(MouseEvent event)
+ {
+ //TODO: PDZ- Fix the issue where panning will slightly rotate the object (might be an issue with the camera)
+ if(mousePressed && (mouseButton == LEFT))
+ {
+ if(startMouseX < 1)
+ {
+ startMouseX = mouseX;
+ }
+ float deltaX = mouseX - startMouseX;
+ cameraX += deltaX;
+ startMouseX = mouseX;
+
+ if(startMouseY < 1)
+ {
+ startMouseY = mouseY;
+ }
+ float deltaY = mouseY - startMouseY;
+ cameraY += deltaY;
+ startMouseY = mouseY;
+ }
+ else if(mousePressed && (mouseButton == RIGHT))
+ {
+
+ }
+ }
+
+ void mouseClicked(MouseEvent event)
+ {
+ startMouseX = mouseX;
+ startMouseY = mouseY;
+ }
+
+
+ void draw()
+ {
+ background(0);
+
+ //PDZ- camera needs to be first since the translation messes up the coordinate
+ pushMatrix();
+ camera(WIDTH / 2, HEIGHT / 2, 40f, WIDTH/2.0f, HEIGHT/2.0f, 0f, 0f, 1f, 0f);
+ popMatrix();
+
+ pushMatrix();
+ //TODO: PDZ- see comment above about separating the camera from the curves **SEE ABOVE**
+ // You would just need to apply the translation/rotation in the curve draw methods
+ translate(cameraX, cameraY, cameraZ);
+ stroke(255);
+ strokeWeight(0.2f);
+ rotateX(rotationX);
+ rotateY(rotationY);
+ //sphere(20.0);
+ scale(1); //PDZ- this needs to come after the translation
+ noFill();
+
+ drawBezierCurve();
+ if(stickKnotDrawEnabled)
+ {
+ drawStickKnot();
+ }
+ ////renderCurve.drawBezierCurve();
+ ////renderCurve.drawStickKnot();
+ popMatrix();
+ }
+
+ void drawBezierCurve()
+ {
+ if(bezierDrawPoints != null && bezierDrawPoints.size() > 0)
+ {
+ stroke(255, 0, 0);
+ scale(20);
+ beginShape();
+ for(PVector point : bezierDrawPoints)
+ {
+ vertex(point.x, point.y, point.z);
+ }
+ endShape();
+ }
+ }
+
+ void toggleStickKnotDraw()
+ {
+ stickKnotDrawEnabled = !stickKnotDrawEnabled;
+ }
+
+ void drawStickKnot()
+ {
+ if(stickKnotPoints != null && stickKnotPoints.size() > 0)
+ {
+ stroke(0, 255, 0);
+ scale(1);
+ beginShape();
+ for(PVector point : stickKnotPoints)
+ {
+ vertex(point.x, point.y, point.z);
+ }
+ endShape();
+ }
+ }
\ No newline at end of file
diff --git a/WebContent/params.json b/WebContent/params.json
new file mode 100644
index 0000000..7ca8d75
--- /dev/null
+++ b/WebContent/params.json
@@ -0,0 +1 @@
+{"name":"Knot-Renderer-Frontend","tagline":"","body":"### Welcome to GitHub Pages.\r\nThis automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here [using GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/), select a template crafted by a designer, and publish. After your page is generated, you can check out the new `gh-pages` branch locally. If you’re using GitHub Desktop, simply sync your repository and you’ll see the new branch.\r\n\r\n### Designer Templates\r\nWe’ve crafted some handsome templates for you to use. Go ahead and click 'Continue to layouts' to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved.\r\n\r\n### Creating pages manually\r\nIf you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.\r\n\r\n### Authors and Contributors\r\nYou can @mention a GitHub username to generate a link to their profile. The resulting `` element will link to the contributor’s GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out our [documentation](https://help.github.com/pages) or [contact support](https://github.com/contact) and we’ll help you sort it out.\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}
\ No newline at end of file
diff --git a/WebContent/processing-dev.js b/WebContent/processing-dev.js
new file mode 100644
index 0000000..4dcec10
--- /dev/null
+++ b/WebContent/processing-dev.js
@@ -0,0 +1,21645 @@
+;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o hubfn.$methodArgsIndex ?
+ hubfn.$overloads[hubfn.$methodArgsIndex] : null) ||
+ hubfn.$defaultOverload;
+ return fn.apply(this, arguments);
+ };
+ hubfn.$overloads = overloads;
+ if ("$methodArgsIndex" in basefn) {
+ hubfn.$methodArgsIndex = basefn.$methodArgsIndex;
+ }
+ hubfn.$defaultOverload = defaultOverload;
+ hubfn.name = name;
+ object[name] = hubfn;
+ }
+
+ /**
+ * class overloading, part 2
+ */
+
+ function extendClass(subClass, baseClass) {
+ function extendGetterSetter(propertyName) {
+ defaultScope.defineProperty(subClass, propertyName, {
+ get: function() {
+ return baseClass[propertyName];
+ },
+ set: function(v) {
+ baseClass[propertyName]=v;
+ },
+ enumerable: true
+ });
+ }
+
+ var properties = [];
+ for (var propertyName in baseClass) {
+ if (typeof baseClass[propertyName] === 'function') {
+ overloadBaseClassFunction(subClass, propertyName, baseClass[propertyName]);
+ } else if(propertyName.charAt(0) !== "$" && !(propertyName in subClass)) {
+ // Delaying the properties extension due to the IE9 bug (see #918).
+ properties.push(propertyName);
+ }
+ }
+ while (properties.length > 0) {
+ extendGetterSetter(properties.shift());
+ }
+
+ subClass.$super = baseClass;
+ }
+
+ /**
+ * class overloading, part 3
+ */
+ defaultScope.extendClassChain = function(base) {
+ var path = [base];
+ for (var self = base.$upcast; self; self = self.$upcast) {
+ extendClass(self, base);
+ path.push(self);
+ base = self;
+ }
+ while (path.length > 0) {
+ path.pop().$self=base;
+ }
+ };
+
+ // static
+ defaultScope.extendStaticMembers = function(derived, base) {
+ extendClass(derived, base);
+ };
+
+ // interface
+ defaultScope.extendInterfaceMembers = function(derived, base) {
+ extendClass(derived, base);
+ };
+
+ /**
+ * Java methods and JavaScript functions differ enough that
+ * we need a special function to make sure it all links up
+ * as classical hierarchical class chains.
+ */
+ defaultScope.addMethod = function(object, name, fn, hasMethodArgs) {
+ var existingfn = object[name];
+ if (existingfn || hasMethodArgs) {
+ var args = fn.length;
+ // builds the overload methods table
+ if ("$overloads" in existingfn) {
+ existingfn.$overloads[args] = fn;
+ } else {
+ var hubfn = function() {
+ var fn = hubfn.$overloads[arguments.length] ||
+ ("$methodArgsIndex" in hubfn && arguments.length > hubfn.$methodArgsIndex ?
+ hubfn.$overloads[hubfn.$methodArgsIndex] : null) ||
+ hubfn.$defaultOverload;
+ return fn.apply(this, arguments);
+ };
+ var overloads = [];
+ if (existingfn) {
+ overloads[existingfn.length] = existingfn;
+ }
+ overloads[args] = fn;
+ hubfn.$overloads = overloads;
+ hubfn.$defaultOverload = existingfn || fn;
+ if (hasMethodArgs) {
+ hubfn.$methodArgsIndex = args;
+ }
+ hubfn.name = name;
+ object[name] = hubfn;
+ }
+ } else {
+ object[name] = fn;
+ }
+ };
+
+ // internal helper function
+ function isNumericalJavaType(type) {
+ if (typeof type !== "string") {
+ return false;
+ }
+ return ["byte", "int", "char", "color", "float", "long", "double"].indexOf(type) !== -1;
+ }
+
+ /**
+ * Java's arrays are pre-filled when declared with
+ * an initial size, but no content. JS arrays are not.
+ */
+ defaultScope.createJavaArray = function(type, bounds) {
+ var result = null,
+ defaultValue = null;
+ if (typeof type === "string") {
+ if (type === "boolean") {
+ defaultValue = false;
+ } else if (isNumericalJavaType(type)) {
+ defaultValue = 0;
+ }
+ }
+ if (typeof bounds[0] === 'number') {
+ var itemsCount = 0 | bounds[0];
+ if (bounds.length <= 1) {
+ result = [];
+ result.length = itemsCount;
+ for (var i = 0; i < itemsCount; ++i) {
+ result[i] = defaultValue;
+ }
+ } else {
+ result = [];
+ var newBounds = bounds.slice(1);
+ for (var j = 0; j < itemsCount; ++j) {
+ result.push(defaultScope.createJavaArray(type, newBounds));
+ }
+ }
+ }
+ return result;
+ };
+
+ // screenWidth and screenHeight are shared by all instances.
+ // and return the width/height of the browser's viewport.
+ defaultScope.defineProperty(defaultScope, 'screenWidth',
+ { get: function() { return window.innerWidth; } });
+
+ defaultScope.defineProperty(defaultScope, 'screenHeight',
+ { get: function() { return window.innerHeight; } });
+
+ return defaultScope;
+};
+
+},{}],6:[function(require,module,exports){
+/**
+ * Finalise the Processing.js object.
+ */
+module.exports = function finalizeProcessing(Processing, options) {
+
+ // unpack options
+ var window = options.window,
+ document = options.document,
+ XMLHttpRequest = window.XMLHttpRequest,
+ noop = options.noop,
+ isDOMPresent = options.isDOMPresent,
+ version = options.version,
+ undef;
+
+ // versioning
+ Processing.version = (version ? version : "@DEV-VERSION@");
+
+ // Share lib space
+ Processing.lib = {};
+
+ /**
+ * External libraries can be added to the global Processing
+ * objects with the `registerLibrary` function.
+ */
+ Processing.registerLibrary = function(name, library) {
+ Processing.lib[name] = library;
+ if(library.hasOwnProperty("init")) {
+ library.init(defaultScope);
+ }
+ };
+
+ /**
+ * This is the object that acts as our version of PApplet.
+ * This can be called as Processing.Sketch() or as
+ * Processing.Sketch(function) in which case the function
+ * must be an already-compiled-to-JS sketch function.
+ */
+ Processing.Sketch = function(attachFunction) {
+ this.attachFunction = attachFunction;
+ this.options = {
+ pauseOnBlur: false,
+ globalKeyEvents: false
+ };
+
+ /* Optional Sketch event hooks:
+ * onLoad - parsing/preloading is done, before sketch starts
+ * onSetup - setup() has been called, before first draw()
+ * onPause - noLoop() has been called, pausing draw loop
+ * onLoop - loop() has been called, resuming draw loop
+ * onFrameStart - draw() loop about to begin
+ * onFrameEnd - draw() loop finished
+ * onExit - exit() done being called
+ */
+ this.onLoad = noop;
+ this.onSetup = noop;
+ this.onPause = noop;
+ this.onLoop = noop;
+ this.onFrameStart = noop;
+ this.onFrameEnd = noop;
+ this.onExit = noop;
+
+ this.params = {};
+ this.imageCache = {
+ pending: 0,
+ images: {},
+ // Opera requires special administration for preloading
+ operaCache: {},
+ // Specify an optional img arg if the image is already loaded in the DOM,
+ // otherwise href will get loaded.
+ add: function(href, img) {
+ // Prevent muliple loads for an image, in case it gets
+ // preloaded more than once, or is added via JS and then preloaded.
+ if (this.images[href]) {
+ return;
+ }
+
+ if (!isDOMPresent) {
+ this.images[href] = null;
+ }
+
+ // No image in the DOM, kick-off a background load
+ if (!img) {
+ img = new Image();
+ img.onload = (function(owner) {
+ return function() {
+ owner.pending--;
+ };
+ }(this));
+ this.pending++;
+ img.src = href;
+ }
+
+ this.images[href] = img;
+
+ // Opera will not load images until they are inserted into the DOM.
+ if (window.opera) {
+ var div = document.createElement("div");
+ div.appendChild(img);
+ // we can't use "display: none", since that makes it invisible, and thus not load
+ div.style.position = "absolute";
+ div.style.opacity = 0;
+ div.style.width = "1px";
+ div.style.height= "1px";
+ if (!this.operaCache[href]) {
+ document.body.appendChild(div);
+ this.operaCache[href] = div;
+ }
+ }
+ }
+ };
+
+ this.sourceCode = undefined;
+ this.attach = function(processing) {
+ // either attachFunction or sourceCode must be present on attach
+ if(typeof this.attachFunction === "function") {
+ this.attachFunction(processing);
+ } else if(this.sourceCode) {
+ var func = ((new Function("return (" + this.sourceCode + ");"))());
+ func(processing);
+ this.attachFunction = func;
+ } else {
+ throw "Unable to attach sketch to the processing instance";
+ }
+ };
+
+ this.toString = function() {
+ var i;
+ var code = "((function(Sketch) {\n";
+ code += "var sketch = new Sketch(\n" + this.sourceCode + ");\n";
+ for(i in this.options) {
+ if(this.options.hasOwnProperty(i)) {
+ var value = this.options[i];
+ code += "sketch.options." + i + " = " +
+ (typeof value === 'string' ? '\"' + value + '\"' : "" + value) + ";\n";
+ }
+ }
+ for(i in this.imageCache) {
+ if(this.options.hasOwnProperty(i)) {
+ code += "sketch.imageCache.add(\"" + i + "\");\n";
+ }
+ }
+ // TODO serialize fonts
+ code += "return sketch;\n})(Processing.Sketch))";
+ return code;
+ };
+ };
+
+ /**
+ * aggregate all source code into a single file, then rewrite that
+ * source and bind to canvas via new Processing(canvas, sourcestring).
+ * @param {CANVAS} canvas The html canvas element to bind to
+ * @param {String[]} source The array of files that must be loaded
+ */
+ var loadSketchFromSources = Processing.loadSketchFromSources = function(canvas, sources) {
+ var code = [], errors = [], sourcesCount = sources.length, loaded = 0;
+
+ function ajaxAsync(url, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ var error;
+ if (xhr.status !== 200 && xhr.status !== 0) {
+ error = "Invalid XHR status " + xhr.status;
+ } else if (xhr.responseText === "") {
+ // Give a hint when loading fails due to same-origin issues on file:/// urls
+ if ( ("withCredentials" in new XMLHttpRequest()) &&
+ (new XMLHttpRequest()).withCredentials === false &&
+ window.location.protocol === "file:" ) {
+ error = "XMLHttpRequest failure, possibly due to a same-origin policy violation. You can try loading this page in another browser, or load it from http://localhost using a local webserver. See the Processing.js README for a more detailed explanation of this problem and solutions.";
+ } else {
+ error = "File is empty.";
+ }
+ }
+
+ callback(xhr.responseText, error);
+ }
+ };
+ xhr.open("GET", url, true);
+ if (xhr.overrideMimeType) {
+ xhr.overrideMimeType("application/json");
+ }
+ xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); // no cache
+ xhr.send(null);
+ }
+
+ function loadBlock(index, filename) {
+ function callback(block, error) {
+ code[index] = block;
+ ++loaded;
+ if (error) {
+ errors.push(filename + " ==> " + error);
+ }
+ if (loaded === sourcesCount) {
+ if (errors.length === 0) {
+ try {
+ return new Processing(canvas, code.join("\n"));
+ } catch(e) {
+ console.log("Processing.js: Unable to execute pjs sketch.");
+ throw e;
+ }
+ } else {
+ throw "Processing.js: Unable to load pjs sketch files: " + errors.join("\n");
+ }
+ }
+ }
+ if (filename.charAt(0) === '#') {
+ // trying to get script from the element
+ var scriptElement = document.getElementById(filename.substring(1));
+ if (scriptElement) {
+ callback(scriptElement.text || scriptElement.textContent);
+ } else {
+ callback("", "Unable to load pjs sketch: element with id \'" + filename.substring(1) + "\' was not found");
+ }
+ return;
+ }
+
+ ajaxAsync(filename, callback);
+ }
+
+ for (var i = 0; i < sourcesCount; ++i) {
+ loadBlock(i, sources[i]);
+ }
+ };
+
+ /**
+ * Automatic initialization function.
+ */
+ var init = function() {
+ document.removeEventListener('DOMContentLoaded', init, false);
+
+ // before running through init, clear the instances list, to prevent
+ // sketch duplication when page content is dynamically swapped without
+ // swapping out processing.js
+ processingInstances = [];
+ Processing.instances = processingInstances;
+
+ var canvas = document.getElementsByTagName('canvas'),
+ filenames;
+
+ for (var i = 0, l = canvas.length; i < l; i++) {
+ // datasrc and data-src are deprecated.
+ var processingSources = canvas[i].getAttribute('data-processing-sources');
+ if (processingSources === null) {
+ // Temporary fallback for datasrc and data-src
+ processingSources = canvas[i].getAttribute('data-src');
+ if (processingSources === null) {
+ processingSources = canvas[i].getAttribute('datasrc');
+ }
+ }
+ if (processingSources) {
+ filenames = processingSources.split(/\s+/g);
+ for (var j = 0; j < filenames.length;) {
+ if (filenames[j]) {
+ j++;
+ } else {
+ filenames.splice(j, 1);
+ }
+ }
+ loadSketchFromSources(canvas[i], filenames);
+ }
+ }
+
+ // also process all