/*
@overview Amino: JavaScript Scenegraph
Amino is a scenegraph for drawing 2D graphics in JavaScript with the
HTML 5 Canvas API. By creating a tree of nodes, you can draw shapes, text, images special effects; complete with transforms and animation.
Amino takes care of all rendering, animation, and event handling
so you can build *rich* interactive graphics with very little code.
Using Amino is much more convenient than writing canvas code by hand.
Here's a quick example:
A note on properties. Most objects have properties like `x` or `width`.
Properties are accessed with getters. For example, to access the `width`
property on a rectangle, call `rect.getWidth()`. Properties are set
with setters. For example, to set the `width` property
on a rectangle, call `rect.setWidth(100)`. Most functions, especially
property setters, are chainable. This means you
can set a bunch of properties at once like this:
var c = new Rect()
.setX(50)
.setY(50)
.setWidth(100)
.setHeight(200)
.setFill("green")
.setStrokeWidth(5)
.setStroke("black")
;
@end
*/
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
function attachEvent(node,name,func) {
//self.masterListeners.push(func);
if(node.addEventListener) {
node.addEventListener(name,func,false);
} else if(node.attachEvent) {
node.attachEvent(name,func);
}
};
// 'extend' is From Jo lib, by Dave Balmer
// syntactic sugar to make it easier to extend a class
Function.prototype.extend = function(superclass, proto) {
// create our new subclass
this.prototype = new superclass();
/*
// optional subclass methods and properties
if (proto) {
for (var i in proto)
this.prototype[i] = proto[i];
}
*/
};
/*
@class Amino The engine that drives the whole system.
You generally only need one of these per page.
@end
*/
function Amino() {
this.canvases = [];
this.anims = [];
this.timeout = 1000/30;
this.autoPaint = false;
this.isTouchEnabled = "ontouchend" in document;
}
//@function addCanvas adds a new canvas to the engine. Pass in the string id of a canvas element in the page.
Amino.prototype.addCanvas = function(id) {
var canvasElement = document.getElementById(id);
var canvas = new Canvas(this,canvasElement);
this.canvases.push(canvas);
return canvas;
}
//@function addAnim adds a new animation to then engine.
Amino.prototype.addAnim = function(anim) {
anim.engine = this;
this.anims.push(anim);
return this;
}
Amino.prototype.removeAnim = function(anim) {
var index = this.anims.indexOf(anim);
this.anims.splice(index,1);
return this;
}
Amino.prototype.start = function() {
var self = this;
var rp = function() {
self.repaint();
window.requestAnimationFrame(rp);
}
if(this.autoPaint) {
rp();
} else {
//just paint once
this.repaint();
}
}
Amino.prototype.repaint = function() {
var animRunning = false;
for(var i=0; i=0; i--) {
var node = this.nodes[i];
if(node && node.isVisible() && node.contains(point)) {
return node;
}
if(node instanceof Group && node.isVisible()) {
var r = this.searchGroup(node,point);
if(r) {
return r;
}
}
}
return this;
}
this.searchGroup = function(group,point) {
point = {x:point.x-group.getX(), y:point.y-group.getY() };
for(var j=group.children.length-1; j>=0; j--) {
var node = group.children[j];
if(node && node.isVisible() && node.contains(point)) {
return node;
}
if(node instanceof Group && node.isVisible()) {
var r = this.searchGroup(node,point);
if(r) return r;
}
}
return null;
}
}
Canvas.prototype.repaint = function() {
var ctx = this.domCanvas.getContext('2d');
this.width = this.domCanvas.width;
this.height = this.domCanvas.height;
ctx.fillStyle = this.bgfill;
if(this.transparent) {
ctx.clearRect(0,0,this.width,this.height);
} else {
ctx.fillRect(0,0,this.width,this.height);
}
ctx.can = this;
ctx.engine = this.engine;
ctx.save();
//ctx.rect(0,0,100,100);
//ctx.clip();
for(var i=0; i 0) {
if(self.stroke.generate) {
ctx.strokeStyle = self.stroke.generate(ctx);
} else {
ctx.strokeStyle = self.stroke;
}
ctx.lineWidth = self.strokeWidth;
self.strokeShape(ctx);
}
}
}
AminoShape.extend(AminoNode);
AminoShape.prototype.getFill = function() {
return this.fill;
}
AminoShape.prototype.setStroke = function(stroke) {
this.stroke = stroke;
this.setDirty();
return this;
}
AminoShape.prototype.getStroke = function() {
return this.stroke;
}
AminoShape.prototype.setStrokeWidth = function(strokeWidth) {
this.strokeWidth = strokeWidth;
this.setDirty();
return this;
}
AminoShape.prototype.getStrokeWidth = function() {
return this.strokeWidth;
}
AminoShape.prototype.contains = function(ctx) {
return false;
}
function Transform(n) {
this.node = n;
this.node.parent = this;
this.typename = "Transform";
var self = this;
//@property translateX translate in the X direction
this.translateX = 0;
this.setTranslateX = function(tx) {
self.translateX = tx;
self.setDirty();
return self;
};
this.getTranslateX = function() {
return this.translateX;
};
//@property translateY translate in the Y direction
this.translateY = 0;
this.setTranslateY = function(ty) {
this.translateY = ty;
this.setDirty();
return this;
};
this.getTranslateY = function() {
return this.translateY;
};
//@property scaleX scale in the X direction
this.scaleX = 1;
this.setScaleX = function(sx) {
this.scaleX = sx;
this.setDirty();
return this;
};
this.getScaleX = function() {
return this.scaleX;
};
//@property scaleY scale in the X direction
this.scaleY = 1;
this.setScaleY = function(sy) {
this.scaleY = sy;
this.setDirty();
return this;
};
this.getScaleY = function() {
return this.scaleY;
};
//@property anchorX scale in the X direction
this.anchorX = 0;
this.setAnchorX = function(sx) {
this.anchorX = sx;
this.setDirty();
return this;
};
this.getAnchorX = function() {
return this.anchorX;
};
//@property anchorY scale in the X direction
this.anchorY = 0;
this.setAnchorY = function(sy) {
this.anchorY = sy;
this.setDirty();
return this;
};
this.getAnchorY = function() {
return this.anchorY;
};
//@property rotate set the rotation, in degrees
this.rotate = 0;
this.setRotate = function(rotate) {
this.rotate = rotate;
this.setDirty();
return this;
};
this.getRotate = function() {
return this.rotate;
};
/* container stuff */
this.contains = function(x,y) {
return false;
};
this.hasChildren = function() {
return true;
};
this.childCount = function() {
return 1;
};
this.getChild = function(n) {
return this.node;
};
this.paint = function(ctx) {
ctx.save();
ctx.translate(self.translateX,self.translateY);
ctx.translate(self.anchorX,self.anchorY);
var r = this.rotate % 360;
ctx.rotate(r*Math.PI/180.0,0,0);
if(self.scaleX != 1 || self.scaleY != 1) {
ctx.scale(self.scaleX,self.scaleY);
}
ctx.translate(-self.anchorX,-self.anchorY);
self.node.paint(ctx);
ctx.restore();
};
return true;
}
Transform.extend(AminoNode);
Transform.prototype.setDirty = function() {
if(this.parent != null) {
this.parent.setDirty();
}
}
/*
@class Group A parent node which holds an ordered list of child nodes. It does not draw anything by itself, but setting visible to false will hide the children.
@category shape
*/
function Group() {
AminoNode.call(this);
this.typename = "Group";
this.children = [];
this.parent = null;
var self = this;
//@property x set the x coordinate of the group.
this.x = 0;
this.setX = function(x) {
self.x = x;
self.setDirty();
return self;
};
this.getX = function() {
return self.x;
};
//@property y set the y coordinate of the group.
this.y = 0;
this.setY = function(y) {
self.y = y;
self.setDirty();
return self;
};
this.getY = function() {
return self.y;
};
this.opacity = 1.0;
this.setOpacity = function(o) {
self.opacity = o;
return self;
};
this.getOpacity = function() {
return self.opacity;
};
//@method Add the child `n` to this group.
this.add = function(n) {
self.children[self.children.length] = n;
n.setParent(self);
self.setDirty();
return self;
};
//@method Remove the child `n` from this group.
this.remove = function(n) {
var i = self.children.indexOf(n);
if(i >= 0) {
self.children.splice(i,1);
n.setParent(null);
}
self.setDirty();
return self;
};
this.paint = function(ctx) {
if(!self.isVisible()) return;
var ga = ctx.globalAlpha;
ctx.globalAlpha = self.opacity;
ctx.translate(self.x,self.y);
for(var i=0; i this.duration*1000) {
this.started = false;
if(this.afterCallback) {
this.afterCallback();
}
//don't loop
if(this.loop == 0 || this.loopcount == 0) {
this.playing = false;
}
//loop forver
if(this.loop == -1) {
//no nothing
}
//loop N times
if(this.loop > 0) {
this.loopcount--;
}
if(this.autoReverse) {
this.forward = !this.forward;
}
return;
}
var t = (currentTime-this.startTime)/(this.duration*1000);
if(!this.forward) t = 1-t;
var val = this.startValue + t*(this.end-this.startValue);
if(this.isdom) {
this.node.style[this.prop] = (val+"px");
} else {
var fun = "set"
+this.prop[0].toUpperCase()
+this.prop.slice(1);
this.node[fun](val);
}
}
PropAnim.prototype.toggle = function() {
this.playing = !this.playing;
this.engine.animationChanged();
}
PropAnim.prototype.start = function() {
this.playing = true;
if(this.engine) {
this.engine.animationChanged();
}
return this;
}
PropAnim.prototype.onBefore = function(beforeCallback) {
this.beforeCallback = beforeCallback;
return this;
}
PropAnim.prototype.onAfter = function(afterCallback) {
this.afterCallback = afterCallback;
return this;
}
PropAnim.prototype.setLoop = function(loop) {
this.loop = loop;
this.loopcount = loop;
return this;
}
PropAnim.prototype.setAutoReverse = function(autoReverse) {
this.autoReverse = true;
return this;
}
function SerialAnim() {
this.anims = [];
this.animIndex = -1;
}
SerialAnim.prototype.add = function(anim) {
this.anims.push(anim);
return this;
}
SerialAnim.prototype.start = function() {
this.playing = true;
this.animIndex = 0;
this.anims[this.animIndex].start();
if(this.engine) {
this.engine.animationChanged();
}
return this;
}
SerialAnim.prototype.update = function() {
if(!this.playing) return;
if(!this.started) {
this.started = true;
}
var anim = this.anims[this.animIndex];
anim.update();
if(!anim.playing) {
this.animIndex++;
if(this.animIndex >= this.anims.length) {
console.log('serial anim done');
this.playing = false;
} else {
this.anims[this.animIndex].start();
}
}
}
function ParallelAnim() {
this.anims = [];
}
ParallelAnim.prototype.add = function(anim) {
this.anims.push(anim);
return this;
}
ParallelAnim.prototype.start = function() {
this.playing = true;
for(var i=0; ibuf2
}
ctx.save();
ctx.translate(bounds.getX(),bounds.getY());
ctx.drawImage(this.buf2.buffer,0,0);
ctx.restore();
this.clearDirty();
};
this.applyEffect = function(buf, buf2, radius) {
console.log("buf = " + buf + " "+ buf.getWidth());
var data = buf.getData();
var s = radius*2;
var size = 0;
var scale = 1-this.getSaturation();
for(var x = 0+size; x 1.0) this.saturation = 1.0;
if(this.saturation < 0.0) this.saturation = 0.0;
this.setDirty();
return this;
};
this.getSaturation = function() {
return this.saturation;
};
//@property brightness value between -1 and 1
this.brightness = 0;
this.setBrightness = function(b) {
this.brightness = b;
if(this.brightness < -1.0) this.brightness = -1.0;
if(this.brightness > 1.0) this.brightness = 1.0;
this.setDirty();
return this;
};
this.getBrightness = function() { return this.brightness; };
//@property contrast value between 0 and 10. default is 1
this.contrast = 0;
this.setContrast = function(c) {
this.contrast = c;
if(this.contrast < 0.0) this.contrast = 0.0;
if(this.contrast > 10.0) this.contrast = 10.0;
this.setDirty();
return this;
};
this.getContrast = function() { return this.contrast; }
this.inProgress = false;
this.workX = 0;
this.workY = 0;
this.startTime = 0;
var self = this;
this.draw = function(ctx) {
var bounds = this.node.getVisualBounds();
if(!this.buf1 || bounds.getWidth() != this.buf1.getWidth()) {
this.buf1 = new Buffer(
bounds.getWidth()
,bounds.getHeight()
);
this.buf2 = new Buffer(
bounds.getWidth()
,bounds.getHeight()
);
}
//redraw the child only if it's dirty
if(this.isDirty()) {
this.startTime = new Date().getTime();
//render child into first buffer
this.buf1.clear();
var ctx1 = this.buf1.getContext();
ctx1.save();
ctx1.translate(
-bounds.getX()
,-bounds.getY());
this.node.draw(ctx1);
ctx1.restore();
//apply affect from buf1 into buf2
//this.buf2.clear();
//console.log("marking in progress again");
this.workX = 0;
this.workY = 0;
this.inProgress = true;
}
if(this.inProgress) {
var start = new Date().getTime();
while(new Date().getTime()-start < 1000/40) {
var workSize = 32;
var workW = workSize;
if(this.workX+workW > this.buf1.getWidth()) {
workW = this.buf1.getWidth()-this.workX;
}
var workH = workSize;
if(this.workY+workH > this.buf1.getHeight()) {
workH = this.buf1.getHeight()-this.workY;
}
var tile = new WorkTile(this.workX,this.workY,workW,workH, this.buf1, this.buf2);
this.applyEffect(tile);
if(this.workX+workSize > this.buf1.getWidth()) {
this.workX = 0;
this.workY+=workSize;
} else {
this.workX+=workSize;
}
if(this.workY > this.buf1.getHeight()) {
this.inProgress = false;
var endTime = new Date().getTime();
if(bounds.getWidth() > 100) {
//win.alert("done!: " + (endTime-this.startTime));
}
break;
}
}
}
ctx.save();
ctx.translate(bounds.getX()+self.x,bounds.getY()+self.y);
ctx.drawImage(this.buf2.buffer,0,0);
ctx.restore();
this.clearDirty();
};
this.applyEffect = function(tile) {
var buf = tile.src;
var buf2 = tile.dst;
var workSize = tile.width;
var data = tile.getData();
var d = data.data;
var tw = tile.width;
var th = tile.height;
var scale = 1-this.getSaturation();
var bright = this.getBrightness()*256;
var contrast = this.getContrast();
var scale1 = 1-scale;
var r = 0;
var g = 0;
var b = 0;
var a = 0;
for(var x=0; x 0xFF) r = 0xFF;
if(g > 0xFF) g = 0xFF;
if(b > 0xFF) b = 0xFF;
if(r < 0x00) r = 0x00;
if(g < 0x00) g = 0x00;
if(b < 0x00) b = 0x00;
a = 0xFF;
d[pi+0] = r;
d[pi+1] = g;
d[pi+2] = b;
d[pi+3] = a;
}
}
tile.saveData();
};
return true;
}
BackgroundSaturationNode.extend(AminoNode);
/*
@class BlurNode A parent node which blurs its child.
@category effect
*/
function BlurNode(n) {
this.node = n;
console.log("n = " + n);
AminoNode.call(this);
if(n) n.setParent(this);
this.buf1 = null;
this.buf2 = null;
//@property blurRadius the radius of the blur
this.blurRadius = 3;
this.setBlurRadius = function(r) { this.blurRadius = r; return this; }
var self = this;
this.draw = function(ctx) {
var bounds = this.node.getVisualBounds();
if(!this.buf1) {
this.buf1 = new Buffer(
bounds.getWidth()+this.blurRadius*4
,bounds.getHeight()+this.blurRadius*4
);
this.buf2 = new Buffer(
bounds.getWidth()+this.blurRadius*4
,bounds.getHeight()+this.blurRadius*4
);
}
//redraw the child only if it's dirty
if(this.isDirty()) {
//render child into first buffer
this.buf1.clear();
var ctx1 = this.buf1.getContext();
ctx1.save();
ctx1.translate(
-bounds.getX()+this.blurRadius*2
,-bounds.getY()+this.blurRadius*2);
this.node.draw(ctx1);
ctx1.restore();
//apply affect from buf1 into buf2
this.buf2.clear();
this.applyEffect(this.buf1,this.buf2,this.blurRadius);
//buf1->buf2
}
ctx.save();
ctx.translate(bounds.getX(),bounds.getY());
ctx.drawImage(this.buf2.buffer,0,0);
ctx.restore();
this.clearDirty();
};
this.applyEffect = function(buf, buf2, radius) {
var data = buf.getData();
var s = radius*2;
var size = s/2;
for(var x = 0+size; xbuf2
this.applyEffect(this.buf1,this.buf2,this.blurRadius);
//draw child over blur in buf2
var ctx2 = this.buf2.getContext();
ctx2.save();
ctx2.translate(
-bounds.getX()+this.blurRadius*2
,-bounds.getY()+this.blurRadius*2);
this.node.draw(ctx2);
ctx2.restore();
}
ctx.save();
ctx.translate(bounds.getX(),bounds.getY());
ctx.drawImage(this.buf2.buffer,0,0);
ctx.restore();
this.clearDirty();
};
this.applyEffect = function(buf, buf2, radius) {
var data = buf.getData();
var s = radius*2;
var size = s/2;
for(var x = 0+size; x= this.x && pt.x <= this.x + this.w) {
if(pt.y >= this.y && pt.y<=this.y + this.h) {
return true;
}
}
return false;
}
return this;
}
Rect.extend(AminoShape);
function Text() {
AminoShape.call(this);
this.x = 0;
this.y = 0;
this.setX = function(x) {
this.x = x;
return this;
};
this.getX = function() {
return this.x;
}
this.setY = function(y) {
this.y = y;
return this;
};
this.getY = function() {
return this.y;
}
this.text = "random text";
return this;
}
Text.extend(AminoShape);
Text.prototype.set = function(text,x,y) {
this.x = x;
this.y = y;
this.text = text;
return this;
}
Text.prototype.setText = function(text) {
this.text = text;
return this;
}
Text.prototype.setFont = function(font) {
this.font = font;
return this;
}
Text.prototype.fillShape = function(g) {
g.fillStyle = this.fill;
g.font = "12pt sans-serif";
g.fillText(this.text,this.x,this.y);
}
Text.prototype.contains = function(pt) {
return false;
}
function Circle() {
AminoShape.call(this);
this.x = 0;
this.y = 0;
this.radius = 10;
this.getX = function() {
return this.x;
}
this.getY = function() {
return this.y;
}
this.setX = function(x) {
this.x = x;
return this;
}
this.setY = function(y) {
this.y = y;
return this;
}
return this;
}
Circle.extend(AminoShape);
Circle.prototype.set = function(x,y,radius) {
this.x = x;
this.y = y;
this.radius = radius;
return this;
}
Circle.prototype.fillShape = function(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
Circle.prototype.strokeShape = function(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, true);
ctx.closePath();
ctx.stroke();
}
Circle.prototype.contains = function(pt) {
if(pt.x >= this.x-this.radius && pt.x <= this.x + this.radius) {
if(pt.y >= this.y-this.radius && pt.y<=this.y + this.radius) {
return true;
}
}
return false;
}
// ===================== Path and PathNode
var SEGMENT_MOVETO = 1;
var SEGMENT_LINETO = 2;
var SEGMENT_CLOSETO = 3;
var SEGMENT_CURVETO = 4;
function Segment(kind,x,y,a,b,c,d) {
this.kind = kind;
this.x = x;
this.y = y;
if(kind == SEGMENT_CURVETO) {
this.cx1 = x;
this.cy1 = y;
this.cx2 = a;
this.cy2 = b;
this.x = c;
this.y = d;
}
};
/*
@class Path A Path is a sequence of line and curve segments. It is used for drawing arbitrary shapes and animating. Path objects are immutable. You should create them and then reuse them.
@category resource
*/
function Path() {
this.segments = [];
this.closed = false;
//@doc jump directly to the x and y. This is usually the first thing in your path.
this.moveTo = function(x,y) {
this.segments.push(new Segment(SEGMENT_MOVETO,x,y));
return this;
};
//@doc draw a line from the previous x and y to the new x and y.
this.lineTo = function(x,y) {
this.segments.push(new Segment(SEGMENT_LINETO,x,y));
return this;
};
//@doc close the path. It will draw a line from the last x,y to the first x,y if needed.
this.closeTo = function(x,y) {
this.segments.push(new Segment(SEGMENT_CLOSETO,x,y));
this.closed = true;
return this;
};
//@doc draw a beizer curve from the previous x,y to a new point (x2,y2) using the four control points (cx1,cy1,cx2,cy2).
this.curveTo = function(cx1,cy1,cx2,cy2,x2,y2) {
this.segments.push(new Segment(SEGMENT_CURVETO,cx1,cy1,cx2,cy2,x2,y2));
return this;
};
//@doc build the final path object.
this.build = function() {
return this;
};
this.pointAtT = function(fract) {
if(fract >= 1.0 || fract < 0) return [0,0];
var segIndex = 0;
segIndex = Math.floor(fract*(this.segments.length-1));
var segFract = (fract*(this.segments.length-1))-segIndex;
//console.log("seg index = " + (segIndex+1) + " f=" + fract + " sgf=" + segFract);// + " type=" + this.segments[segIndex+1].kind);
var seg = this.segments[segIndex+1];
var prev;
var cur;
switch (seg.kind) {
case SEGMENT_MOVETO: return [0,0];
case SEGMENT_LINETO:
prev = this.segments[segIndex];
cur = seg;
return this.interpLinear(prev.x,prev.y,cur.x,cur.y,segFract);
case SEGMENT_CURVETO:
prev = this.segments[segIndex];
cur = seg;
return this.interpCurve(prev.x,prev.y,cur.cx1,cur.cy1,cur.cx2,cur.cy2, cur.x, cur.y,segFract);
case SEGMENT_CLOSETO:
prev = this.segments[segIndex];
cur = this.segments[0];
return this.interpLinear(prev.x,prev.y,cur.x,cur.y,segFract);
}
return [10,10];
};
this.interpLinear = function(x1, y1, x2, y2, fract) {
return [ (x2-x1)*fract + x1, (y2-y1)*fract + y1 ];
}
this.interpCurve = function( x1, y1, cx1, cy1, cx2, cy2, x2, y2, fract) {
return getBezier(fract, [x2,y2], [cx2,cy2], [cx1,cy1], [x1,y1] );
}
return true;
};
function B1(t) { return t*t*t; }
function B2(t) { return 3*t*t*(1-t); }
function B3(t) { return 3*t*(1-t)*(1-t); }
function B4(t) { return (1-t)*(1-t)*(1-t); }
function getBezier(percent, C1, C2, C3, C4) {
var pos = [];
pos[0] = C1[0]*B1(percent) + C2[0]*B2(percent) + C3[0]*B3(percent) + C4[0]*B4(percent);
pos[1] = C1[1]*B1(percent) + C2[1]*B2(percent) + C3[1]*B3(percent) + C4[1]*B4(percent);
return pos;
}
/*
@class PathNode Draws a path.
@category shape
*/
function PathNode() {
AminoShape.call(this);
//@property path the Path to draw
this.path = null;
this._bounds = null;
this.setPath = function(path) {
this.path = path;
this._bounds = null;
this.setDirty();
return this;
};
this.getPath = function() {
return this.path;
};
/*
this.getVisualBounds = function() {
if(this._bounds == null) {
var l = 10000;
var r = -10000;
var t = 10000;
var b = -10000;
for(var i=0; i= self.x && pt.x <= self.x + self.width) {
if(pt.y >= self.y && pt.y<=self.y + self.height) {
return true;
}
}
return false;
}
return true;
};
ImageView.extend(AminoNode);
// =========== Paints ===========
function LinearGradientFill(x,y,width,height) {
var self = this;
self.x = x;
self.y = y;
self.width = width;
self.height = height;
self.offsets = [];
self.colors = [];
self.addStop = function(offset, color) {
self.offsets.push(offset);
self.colors.push(color);
return self;
};
self.generate = function(ctx) {
var grad = ctx.createLinearGradient(
self.x,self.y,
self.width,self.height);
for(var i in self.offsets) {
grad.addColorStop(self.offsets[i],self.colors[i]);
}
return grad;
}
};
function RadialGradientFill(x,y,radius) {
var self = this;
self.x = x;
self.y = y;
self.radius = radius;
self.offsets = [];
self.colors = [];
self.addStop = function(offset, color) {
self.offsets.push(offset);
self.colors.push(color);
return self;
};
self.generate = function(ctx) {
var grad = ctx.createRadialGradient(self.x,self.y, 0, self.x, self.y, self.radius);
for(var i in self.offsets) {
grad.addColorStop(self.offsets[i],self.colors[i]);
}
return grad;
}
};
function PatternFill(url, repeat) {
var self = this;
this.src = url;
this.img = new Image();
this.loaded = false;
this.repeat = repeat;
this.img.onload = function() {
self.loaded = true;
if(self.can != null) {
self.can.setDirty();
}
};
this.can = null;
this.img.src = self.src;
this.generate = function(ctx) {
self.can = ctx.can;
if(!self.loaded) {
return "red";
}
return ctx.createPattern(self.img, self.repeat);
};
return true;
}
function ThreeScene() {
this.scene = new THREE.Scene();
this.renderer = null;
this.engine = null;
this.repaint = function() {
this.dirty = false;
this.renderer.render( this.scene, this.camera );
};
this.add = function(o) {
if(o instanceof THREE.DirectionalLight) {
this.scene.add(o);
return;
}
this.scene.add(o.obj);
o.parent = this;
};
this.setDirty = function() {
if(!this.dirty) {
this.dirty = true;
if(!this.engine.autoPaint) {
this.repaint();
}
}
};
}
Amino.prototype.add3DCanvas = function(id) {
var canvasElement = document.getElementById(id);
var width = canvasElement.clientWidth;
var height = canvasElement.clientHeight;
sc = new ThreeScene();
sc.engine = this;
sc.scene.domWidth = width;
sc.scene.domHeight = height;
sc.camera = new THREE.PerspectiveCamera( 70, width/height, 1, 1000 );
sc.camera.position.y = 150;
sc.camera.position.z = 500;
sc.scene.add( sc.camera );
sc.domElement = canvasElement;
sc.renderer = new THREE.CanvasRenderer();
sc.renderer.setSize( width, height);
sc.domElement.appendChild( sc.renderer.domElement );
this.canvases.push(sc);
return sc;
};
function BaseNodeThree() {
this.parent = null;
this.setFill = function(color) {
if(color[0] == '#') {
color = color.substr(1);
}
this.fill = parseInt(color,16);
return this;
};
this.setDirty = function() {
if(this.parent != null) {
this.parent.setDirty();
}
};
};
function Block() {
this.geometry = new THREE.CubeGeometry( 200, 200, 200 );
this.fill = 0xff0000;
this.obj = null;
this.set = function(w,h,d) {
this.geometry = new THREE.CubeGeometry( w, h, d);
return this;
};
this.getThreeObj = function() {
if(this.obj == null) {
this.material = new THREE.MeshLambertMaterial( {
color: this.fill,
shading: THREE.FlatShading,
overdraw: true });
this.obj = new THREE.Mesh(this.geometry, this.material);
}
return this.obj;
};
}
Block.extend(BaseNodeThree);
function Sphere() {
this.geometry = new THREE.SphereGeometry(200, 50, 50)
this.fill = 0xff0000;
this.obj = null;
this.set = function(radius, detail) {
this.geometry = new THREE.SphereGeometry(radius, detail, detail)
return this;
};
this.getThreeObj = function() {
if(this.obj == null) {
this.material = new THREE.MeshLambertMaterial( {
color: this.fill,
//shading: THREE.SmoothShading,
shading: THREE.FlatShading,
overdraw: true,
});
this.obj = new THREE.Mesh(this.geometry, this.material);
}
return this.obj;
};
}
Sphere.extend(BaseNodeThree);
function BeveledPath() {
var extrudeSettings = {
amount: 20,
bevelEnabled: true,
bevelSegments: 2,
steps: 2 };
var sqLength = 200;
this.squareShape = new THREE.Shape();
this.squareShape.moveTo( -sqLength,-sqLength );
this.squareShape.lineTo( -sqLength, sqLength );
this.squareShape.lineTo( sqLength, sqLength );
this.squareShape.lineTo( sqLength, -sqLength );
this.squareShape.lineTo( -sqLength, -sqLength );
this.geometry = this.squareShape.extrude( extrudeSettings );
this.fill = 0xff0000;
this.obj = null;
this.getThreeObj = function() {
if(this.obj == null) {
this.material = new THREE.MeshLambertMaterial( {
color: this.fill,
shading: THREE.FlatShading,
overdraw: true });
this.obj = new THREE.Mesh(this.geometry, this.material);
}
return this.obj;
};
}
BeveledPath.extend(BaseNodeThree);
function Group3() {
this.obj = new THREE.Object3D();
this.setPositionY = function(y) {
this.obj.position.y = y;
return this;
};
this.add = function(o) {
this.obj.add(o.getThreeObj());
o.parent = this;
return this;
};
this.setRotateY = function(y) {
this.obj.rotation.y = y;
this.setDirty();
return this;
};
}
Group3.extend(BaseNodeThree);
function Light3() {
this.obj = new THREE.DirectionalLight( 0xffffff );
//directly front light
this.obj.position.set(1,0.2,0.5);
}