Greetings experts and fellow programmers...
I experimented a bit with d3.js and I found a nice example for an animated analog clock here:
http://www.google.de/url?sa=t&rct=j&q=d3%20analog%20clock&source=web&cd=1&cad=rja&ved=0CDIQFjAA&url=http%3A%2F%2Fwww.ericbullington.com%2Farticles%2F2012%2F10%2F27%2Fd3-oclock&ei=NNgIUuCqKomsOKmDgMAK&usg=AFQjCNGoI2g7XQIguM_6UM5V_6WzeJyPxA&bvm=bv.50500085,d.bGE
I changed it into 4 clocks, showing local time, London, New York and Hong Kong time simultaneously...
The code (snippet) looks like this:
test.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HEAD>
<META content="IE=10.000" http-equiv="X-UA-Compatible">
<META charset="utf-8">
<META http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<META name="viewport" content="width=device-width, initial-scale=1">
<SCRIPT src="d3.v2.min.js"></SCRIPT>
<SCRIPT src="clock.js"></SCRIPT>
</HEAD>
<BODY>
<DIV class="herechart"></DIV>
<DIV class="londonchart"></DIV>
<DIV class="newyorkchart"></DIV>
<DIV class="hongkongchart"></DIV>
<SCRIPT src="clock.js"></SCRIPT>
</BODY></HTML>
clock.js (revised):
(function() {
var clockGroup, fields, formatHour, formatMinute, formatSecond, height, offSetX, offSetY, pi, render, scaleHours, scaleSecsMins, vis, width;
formatSecond = d3.time.format("%S");
formatMinute = d3.time.format("%M");
formatHour = d3.time.format("%H");
herefields = function() {
var d, data, hour, minute, second;
d = new Date();
second = d.getSeconds();
minute = d.getMinutes();
hour = d.getHours() + minute / 60;
return data = [
{ "unit": "seconds", "text": formatSecond(d), "numeric": second },
{ "unit": "minutes", "text": formatMinute(d), "numeric": minute },
{ "unit": "hours", "text": formatHour(d), "numeric": hour }
];
};
londonfields = function() {
// fields creating in the same way as in herfields function
};
newyorkfields = function() {
// you get the idea
};
hongkongfields ) = function {
// i did it again
};
width = 82;
height = 82;
offSetX = 82;
offSetY = 82;
pi = Math.PI;
scaleSecsMins = d3.scale.linear().domain([0, 59 + 59 / 60]).range([0, 2 * pi]);
scaleHours = d3.scale.linear().domain([0, 11 + 59 / 60]).range([0, 2 * pi]);
// creating local clock frame
vis = d3.selectAll(".herechart").append("svg:svg").attr("width", width).attr("height", height);
clockGroup = vis.append("svg:g").attr("transform", "translate(" + offSetX + "," + offSetY + ")");
// creating London time clock frame
londonvis = d3.selectAll(".londonchart").append("svg:svg").attr("width", width).attr("height", height);
londonGroup = londonvis.append("svg:g").attr("transform", "translate(" + 260 + "," + -4 + ")");
// omitting creation of the two other clock frames
// ommitting creation of clock hands (using .append(...)) and texts alltogether - see web example
// render function
render = function(heredata, londondata, newyorkdata, hongkongdata) {
var hourArc, minuteArc, secondArc;
clockGroup.selectAll(".clockhand").remove();
londonGroup.selectAll(".clockhand").remove();
newyorkGroup.selectAll(".clockhand").remove();
hongkongGroup.selectAll(".clockhand").remove();
secondArc = d3.svg.arc().innerRadius(0).outerRadius(70).startAngle(function(d) { return scaleSecsMins(d.numeric); }).endAngle(function(d) { return scaleSecsMins(d.numeric); });
minuteArc = d3.svg.arc().innerRadius(0).outerRadius(66).startAngle(function(d) { return scaleSecsMins(d.numeric); }).endAngle(function(d) { return scaleSecsMins(d.numeric); });
hourArc = d3.svg.arc().innerRadius(0).outerRadius(50).startAngle(function(d) { return scaleHours(d.numeric % 12); }).endAngle(function(d) { return scaleHours(d.numeric % 12); });
clockGroup.selectAll(".clockhand").data(heredata).enter().append("svg:path").attr("d", function(d) { if (d.unit === "seconds") { return secondArc(d); } else if (d.unit === "minutes") { return minuteArc(d); } else if (d.unit === "hours") { return hourArc(d); }
}).attr("class", "clockhand").attr("stroke", "black").attr("stroke-width", function(d) { if (d.unit === "seconds") { return 1; } else if (d.unit === "minutes") { return 3; } else if (d.unit === "hours") { return 3; } }).attr("fill", "none");
londonGroup.selectAll(".clockhand").data(londondata).enter().append("svg:path").attr("d", function(d) { if (d.unit === "seconds") { return secondArc(d); } else if (d.unit === "minutes") { return minuteArc(d); } else if (d.unit === "hours") { return hourArc(d); }
}).attr("class", "clockhand").attr("stroke", "black").attr("stroke-width", function(d) { if (d.unit === "seconds") { return 1; } else if (d.unit === "minutes") { return 3; } else if (d.unit === "hours") { return 3; } }).attr("fill", "none");
newyorkGroup.selectAll(".clockhand").data(newyorkdata).enter().append("svg:path").attr("d", function(d) { if (d.unit === "seconds") { return secondArc(d); } else if (d.unit === "minutes") { return minuteArc(d); } else if (d.unit === "hours") { return hourArc(d); }
}).attr("class", "clockhand").attr("stroke", "black").attr("stroke-width", function(d) { if (d.unit === "seconds") { return 1; } else if (d.unit === "minutes") { return 3; } else if (d.unit === "hours") { return 3; } }).attr("fill", "none");
hongkongGroup.selectAll(".clockhand").data(hongkongdata).enter().append("svg:path").attr("d", function(d) { if (d.unit === "seconds") { return secondArc(d); } else if (d.unit === "minutes") { return minuteArc(d); } else if (d.unit === "hours") { return hourArc(d); }
}).attr("class", "clockhand").attr("stroke", "black").attr("stroke-width", function(d) { if (d.unit === "seconds") { return 1; } else if (d.unit === "minutes") { return 3; } else if (d.unit === "hours") { return 3; } }).attr("fill", "none");
};
// end of render function
// periodically getting time fields and give them to render function
setInterval(function() {
var data;
heredata = herefields();
londondata = londonfields();
newyorkdata = newyorkfields();
hongkongdata = hongkongfields();
return render(heredata, londondata, newyorkdata, hongkongdata);
}, 1000); }).call(this);
That code work well for me.
But I would LOVE to include that into a three.js project of mine, where I am using two scenes with two different renderers (one canvas, one css3d renderer) in one projection. I am doing a lot of flying and rotatin' around there... :-)
As the D3 objects are DIVs, I thought I can create CSS3D-rendered objects and fly them (TWEENING and changing positions) around with the other objects.
Is there a way to do it? And how can this be done.
Below, I show some of the code that I am using in my three.js project where there is an init() function which CREATES DIVs and scene,renderers and projections... I cut out heavily on designing particles and DIVs plus alot of stuff for changing these elements while animating them.... the code below works very well (not the snippet, but the full code). But how do I combine them?
<body onload="connameinit();">
<script src="build/three.min.js"></script>
<script src="js/renderers/CSS3DRenderer.js"></script>
<script src="js/libs/tween.min.js"></script>
<script>
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 3000 );
camera.position.z = 1000;
scene = new THREE.Scene();
var program = function ( context ) {
myindex++;
context.beginPath();
context.rect( 3, 12, 7, 1);
context.drawImage(backgroundImage01, 0, 0);
context.closePath();
context.fill();
}
group = new THREE.Object3D();
scene.add( group );
for ( var i = 0; i < 400; i++ ) {
particle = new THREE.Particle( new THREE.ParticleCanvasMaterial( { map: new THREE.Texture( generateSprite() ), color: Math.random() * 0x808008 + 0x808080, program: program } ) );
particle.position.x = Math.random() * 6000 - 3000;
particle.position.y = Math.random() * 6000 - 3000;
particle.position.z = Math.random() * 6000 - 5500;
particle.scale.x = particle.scale.y = Math.random() * 3 + 2;
initParticle( particle, i * 10 );
group.add( particle );
myparticles.push( particle );
}
renderer = new THREE.CanvasRenderer();
renderer.setClearColor(0x000000, 1);
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// creating the CSS3D rendered object
scene2 = new THREE.Scene();
var element = document.createElement( 'div' );
element.className = 'element';
element.style.width = Math.floor(window.innerWidth / window.innerHeight * 1400) + 'px';
element.style.height = '1400px';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';
var profile = document.createElement( 'div' );
profile.className = 'profile';
profile.textContent = "";
element.appendChild( profile );
myimgdiv = new THREE.CSS3DObject( element );
myimgdiv.position.x = 0;
myimgdiv.position.y = 0;
myimgdiv.position.z = 0;
scene2.add( myimgdiv );
renderer2 = new THREE.CSS3DRenderer();
renderer2.setSize( window.innerWidth, window.innerHeight );
renderer2.domElement.style.position = 'absolute';
renderer2.domElement.style.top = 0;
document.body.appendChild( renderer2.domElement );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
TWEEN.update();
camera.lookAt( scene.position );
mypindex = mypindex + mypmul;
if (mypindex > 500) {
mymulti = - mymulti;
mypmul = -1;
}
if (mypindex < -500) {
mymulti = - mymulti;
mypmul = 1;
}
group.rotation.x += mymulti * 0.0003;
group.rotation.y += mymulti * 0.0006;
myimgdiv.scale.x += mymulti * 0.0001;
myimgdiv.scale.y += mymulti * 0.0001;
renderer.render( scene, camera );
renderer2.render( scene2, camera );
}
function mydivtransform( targets, duration ) {
var object = myimgdiv;
var target = targets[ 0 ];
new TWEEN.Tween( object.position )
.to( { x: target.position.x, y: target.position.y, z: object.position.z }, duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
</script>
</body>
</html>
Can I create the clock DIVs in the init() loop instead of placing the code inside the body of the document. I would then somehow split the clock.js code and put part of it within the init() loop, but could somebody help me with that? I though, best way would be a third scene, using a third renderer (CSS3D), using the same projector.
But how can I create Three.js mashes from the D3 objects?
And how can I make sure that the animation of the clock hands continues while these Three.js objects are moved or TWEENed?
Thank you in advance for all your help!
Oliver
Related
I'm a beginner using p5js and I'm trying to work with classes. I'm making a game where you have to find and click a 'wanted man', from a crowd.
So basically, a randomizer picks between 7 different types of 'civilians', and it's supposed to remove one of the types from the 'civilians' that have been spawned. After removing the 'wanted man', I want to add one wanted man so that there is only one 'wanted man'.
So the code spawns a bunch of random 'civilians', then it will delete all 'wanted man' types in the array, and add only one of them. I think there is a better way to do this though.
My basic desire is to have a crowd of 'civilians' that run around, - one of which is a 'wanted man' - and you would have to find and click that 'wanted man' (kind of like a hunting/assassination game).
This is the code for the sketch.js file:
var civilians = [];
var page = 0;
var man1img;
var man2img;
var man3img;
var man4img;
var man5img;
var man6img;
var aliemanimg;
var w;
var h;
var spawnCount = 14;
var wantedMan;
var randCiv;
function preload() {
man1img = loadImage("man1.png");
man2img = loadImage("man2.png");
man3img = loadImage("man3.png");
man4img = loadImage("man4.png");
man5img = loadImage("man5.png");
man6img = loadImage("man6.png");
aliemanimg = loadImage("alieman.png");
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function setup() {
createCanvas(windowWidth, windowHeight);
imageMode(CENTER);
// wantedMan = round(random(0, 6));
wantedMan = 0;
for (var i = 0; i < spawnCount; i++) {
randCiv = round(random(0, 6));
w = random(windowWidth);
h = random(windowHeight);
civilians.push(new Civilian(w, h, wantedMan, randCiv));
console.log(wantedMan);
if (civilians[i].isWantedMan()) {
//OVER HERE \/
civilians.splice(i, 1);
}
}
civilians.push(new Civilian(w, h, wantedMan, wantedMan));
}
// page setup
// page 1 : main screen (play, settings, and those stuff)
// page 2 : show chosen civilian
// page 3 : playing
// page 4 : lose
// page 5 : options
function draw() {
background(220, 80, 80);
for (var i = civilians.length - 1; i >= 0; i--) {
civilians[i].update();
civilians[i].show(mouseX, mouseY);
if (civilians[i].clickedOn(mouseX, mouseY)) {
// detect if is right person
console.log("clicked on boi");
if (civilians[i].isWantedMan()) {
console.log("HES WANTED");
} else {
console.log("HES NOT WANTED");
}
}
}
text(round(frameRate()), 20, 20);
//show wanted man
var tempImg = man1img;
if (wantedMan == 1) {
tempImg = man2img;
} else if (wantedMan == 2) {
tempImg = man3img;
} else if (wantedMan == 3) {
tempImg = man4img;
}
if (wantedMan == 4) {
tempImg = man5img;
} else if (wantedMan == 5) {
tempImg = man6img;
} else if (wantedMan == 6) {
tempImg = aliemanimg;
}
image(tempImg, 50, 70, 70, 90);
}
This is the code for the class:
class Civilian {
constructor(x, y, wantedMan, type) {
this.x = x;
this.y = y;
this.w = 47;
this.h = 60;
this.t = {
x: x,
y: y,
};
this.size = 47;
this.moveSpeed = 0.01;
this.moveDist = 20;
this.wantedMan = wantedMan;
this.civilian = type
this.civilianImg = man1img
this.wantedMan = wantedMan
}
update() {
//move target to random position
this.t.x = random(this.t.x - this.moveDist, this.t.x + this.moveDist);
this.t.y = random(this.t.y - this.moveDist, this.t.y + this.moveDist);
//edge detect
if (this.t.x < 0) {
this.t.x += 5;
}
if (this.t.x > width) {
this.t.x -= 5;
}
if (this.t.y < 0) {
this.t.y += 5;
}
if (this.t.y > height) {
this.t.y -= 5;
}
//images position follows target but with easing
this.x += (this.t.x - this.x) * this.moveSpeed;
this.y += (this.t.y - this.y) * this.moveSpeed;
}
show(ex, ey) {
var d = dist(ex, ey, this.x, this.y);
if (d > this.size / 2) {
tint(255, 255, 255);
} else {
tint(0, 255, 0);
}
if(this.civilian == 1) {
this.civilianImg = man2img
} else if(this.civilian == 2) {
this.civilianImg = man3img
} else if(this.civilian ==3) {
this.civilianImg = man4img
} if(this.civilian == 4) {
this.civilianImg = man5img
} else if(this.civilian == 5) {
this.civilianImg = man6img
} else if(this.civilian == 6) {
this.civilianImg = aliemanimg
}
image(this.civilianImg, this.x, this.y, 47, 60);
}
clickedOn(ex, ey) {
var d = dist(ex, ey, this.x, this.y);
return d < this.size / 2 && mouseIsPressed;
}
isWantedMan() {
return this.civilian == this.wantedMan;
}
}
However, whenever I add a .splice(i,1) under the 'for' loop in setup function - to remove the 'wanted man', it shows this error:
"TypeError: Cannot read properties of undefined (reading
'isWantedMan') at /sketch.js:41:22".
isWantedMan() is a function in the Civilian Class, that returns true if the current 'civilian' is wanted. The .splice is supposed to remove a object from the array, when it is a 'wanted man'.
I don't know why this happens. When I replace the .splice code with a console.log() code, then there is no error.
Also there were probably a lot of things that I could have done better in the code.
I am trying to get 3d transform effect of the bacground image on mouse move.
I have checked f.e. jquery.plate tilt.js and many others plugins, BUT, each of them has problem on chronium browsers like Chrome or Opera (it works fine even on IE11 -.-)
See the attachment and please adwise what is that and if it is fixable? The "dots" appear on mouse move (when background moves) randomly but in line within image.
(function($) {
'use strict';
var namespace = 'jquery-plate';
function Plate($element, options) {
this.config(options);
this.$container = $element;
if (this.options.element) {
if (typeof this.options.element === 'string') {
this.$element = this.$container.find(this.options.element);
} else {
this.$element = $(this.options.element);
}
} else {
this.$element = $element;
}
this.originalTransform = this.$element.css('transform');
this.$container
.on('mouseenter.' + namespace, this.onMouseEnter.bind(this))
.on('mouseleave.' + namespace, this.onMouseLeave.bind(this))
.on('mousemove.' + namespace, this.onMouseMove.bind(this));
}
Plate.prototype.config = function(options) {
this.options = $.extend({
inverse: false,
perspective: 500,
maxRotation: 10,
animationDuration: 200
}, this.options, options);
};
Plate.prototype.destroy = function() {
this.$element.css('transform', this.originalTransform);
this.$container.off('.' + namespace);
};
Plate.prototype.update = function(offsetX, offsetY, duration) {
var rotateX;
var rotateY;
if (offsetX || offsetX === 0) {
var height = this.$container.outerHeight();
var py = (offsetY - height / 2) / (height / 2);
rotateX = this.round(this.options.maxRotation * -py);
} else {
rotateY = 0;
}
if (offsetY || offsetY === 0) {
var width = this.$container.outerWidth();
var px = (offsetX - width / 2) / (width / 2);
rotateY = this.round(this.options.maxRotation * px);
} else {
rotateX = 0;
}
if (this.options.inverse) {
rotateX *= -1;
rotateY *= -1;
}
if (duration) {
this.animate(rotateX, rotateY, duration);
} else if (this.animation && this.animation.remaining) {
this.animation.targetX = rotateX;
this.animation.targetY = rotateY;
} else {
this.transform(rotateX, rotateY);
}
};
Plate.prototype.reset = function(duration) {
this.update(null, null, duration);
};
Plate.prototype.transform = function(rotateX, rotateY) {
this.currentX = rotateX;
this.currentY = rotateY;
this.$element.css('transform',
(this.originalTransform && this.originalTransform !== 'none' ? this.originalTransform + ' ' : '') +
'perspective(' + this.options.perspective + 'px) ' +
'rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)'
);
};
Plate.prototype.animate = function(rotateX, rotateY, duration) {
if (duration) {
this.animation = this.animation || {};
var remaining = this.animation.remaining;
this.animation.time = performance.now();
this.animation.remaining = duration || null;
this.animation.targetX = rotateX;
this.animation.targetY = rotateY;
if (!remaining) {
requestAnimationFrame(this.onAnimationFrame.bind(this));
}
} else {
this.transform(rotateX, rotateY);
}
};
Plate.prototype.round = function(number) {
return Math.round(number * 1000) / 1000;
};
Plate.prototype.offsetCoords = function(event) {
var offset = this.$container.offset();
return {
x: event.pageX - offset.left,
y: event.pageY - offset.top
};
};
Plate.prototype.onAnimationFrame = function(timestamp) {
this.animation = this.animation || {};
var delta = timestamp - (this.animation.time || 0);
this.animation.time = timestamp;
var duration = this.animation.remaining || 0;
var percent = Math.min(delta / duration, 1);
var currentX = this.currentX || 0;
var currentY = this.currentY || 0;
var targetX = this.animation.targetX || 0;
var targetY = this.animation.targetY || 0;
var rotateX = this.round(currentX + (targetX - currentX) * percent);
var rotateY = this.round(currentY + (targetY - currentY) * percent);
this.transform(rotateX, rotateY);
var remaining = duration - delta;
this.animation.remaining = remaining > 0 ? remaining : null;
if (remaining > 0) {
requestAnimationFrame(this.onAnimationFrame.bind(this));
}
};
Plate.prototype.onMouseEnter = function(event) {
var offset = this.offsetCoords(event);
this.update(offset.x, offset.y, this.options.animationDuration);
};
Plate.prototype.onMouseLeave = function(event) {
this.reset(this.options.animationDuration);
};
Plate.prototype.onMouseMove = function(event) {
var offset = this.offsetCoords(event);
this.update(offset.x, offset.y);
};
$.fn.plate = function(options) {
return this.each(function() {
var $element = $(this);
var plate = $element.data(namespace);
if (options === 'remove') {
plate.destroy();
$element.data(namespace, null);
} else {
if (!plate) {
plate = new Plate($element, options);
$element.data(namespace, plate);
plate.reset();
} else {
plate.config(options);
}
}
});
};
})(jQuery);
$('#ab12cd').plate()
<div id="ab12cd" styles="width:100%;height:100%">
<img src="http://eskipaper.com/images/dark-background-8.jpg" />
</div>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
// please open in new window, most visible effect you cen see if you move mouse bottom left/right
Update: JSFiddle: https://jsfiddle.net/Qanary/915fg6ka/
I am trying to make my curveText function work (see bottom of this post). It normally works with fabric.js 1.2.0 however when I updated to fabric.js 1.7.9, the curving function locates the text in wrong positions when below two actions executed sequentially.
ACTIONS : - ISSUE 1
-text group scale is changed (I mean dragging the corner points by mouse to change size).
-setText called
fabric js 1.2.0:
fabric js 1.7.9
I debugged it and the reason for that is _updateObjectsCoords in fabricjs because when I removed it from the code and 2 actions I listed above works fine.
ISSUE 2:
But this time I have faced below problem which is group items are not correctly located when adding the text to canvas for the first time.
with _updateObjectsCoords
without _updateObjectsCoords
Here My Function:
var CurvedText = (function() {
function CurvedText( canvas, options ){
this.opts = options || {};
for ( var prop in CurvedText.defaults ) {
if (prop in this.opts) { continue; }
this.opts[prop] = CurvedText.defaults[prop];
}
this.canvas = canvas;
this.group = new fabric.Group([], {selectable: this.opts.selectable,radiusVal:this.opts.radius,spacingVal:this.opts.spacing,textFliping:this.opts.reverse});
this.canvas.add( this.group ) ;
this.canvas.centerObject( this.group );
this.setText( this.opts.text );
this.canvas.setActiveObject( this.group );
this.canvas.getActiveObject().setCoords();
}
CurvedText.prototype.setObj = function(obj)
{
this.group=obj;
};
CurvedText.prototype.setText = function( newText ) {
this.opts.top=this.group.top;
this.opts.left=this.group.left;
while ( newText.length !== 0 && this.group.size() > newText.length ) {
this.group.remove( this.group.item( this.group.size()-1 ) );
}
for ( var i=0; i<newText.length; i++ ){
if ( this.group.item(i) === undefined ){
var letter = new fabric.Text(newText[i], {
selectable: true
});
this.group.add( letter );
}
else{
this.group.item(i).text = newText[i];
}
}
this.opts.text = newText;
this._setFontStyles();
this._render();
};
CurvedText.prototype._setFontStyles = function() {
for ( var i=0; i<this.group.size(); i++ ){
if( this.opts.textStyleName )
{
if( this.opts.textStyleName === 'fontFamily' )
{
this.group.item(i).setFontFamily( this.opts.fontFamily );
}
if( this.opts.textStyleName === 'fontColor' )
{
this.group.item(i).setFill( this.opts.fontColor );
}
}
else
{
this.group.item(i).setFontFamily( this.opts.fontFamily );
this.group.item(i).setFill( this.opts.fontColor );
}
this.group.item(i).setFontSize( this.opts.fontSize );
this.group.item(i).fontWeight = this.opts.fontWeight ;
}
};
CurvedText.prototype._render = function() {
var curAngle=0,angleRadians=0, align=0;
// Object may have been moved with drag&drop
if ( this.group.hasMoved() ) {
this.opts.top = this.group.top;
this.opts.left = this.group.left;
}
this.opts.angle = this.group.getAngle();
this.opts.scaleX = this.group.scaleX;
this.opts.scaleY = this.group.scaleY;
this.opts.radius = this.group.radiusVal;
this.opts.spacing = this.group.spacingVal;
this.opts.reverse = this.group.textFliping;
// Text align
if ( this.opts.align === 'center' ) {
align = ( this.opts.spacing / 2) * ( this.group.size() - 1) ;
} else if ( this.opts.align === 'right' ) {
align = ( this.opts.spacing ) * ( this.group.size() - 1) ;
}
for ( var i=0; i<this.group.size(); i++) {
// Find coords of each letters (radians : angle*(Math.PI / 180)
if ( this.opts.reverse ) {
curAngle = (-i * parseInt( this.opts.spacing, 10 )) + align;
angleRadians = curAngle * (Math.PI / 180);
this.group.item(i).setAngle( curAngle );
this.group.item(i).set( 'top', (Math.cos( angleRadians ) * this.opts.radius) );
this.group.item(i).set( 'left', (-Math.sin( angleRadians ) * this.opts.radius) );
} else {
curAngle = (i * parseInt( this.opts.spacing, 10)) - align;
angleRadians = curAngle * (Math.PI / 180);
this.group.item(i).setAngle( curAngle );
this.group.item(i).set( 'top', (-Math.cos( angleRadians ) * this.opts.radius) );
this.group.item(i).set( 'left', (Math.sin( angleRadians ) * this.opts.radius) ) ;
}
}
// Update group coords
this.group._calcBounds();
this.group._updateObjectsCoords();
this.group.top = this.opts.top;
this.group.left = this.opts.left;
this.group.saveCoords();
this.canvas.renderAll();
};
CurvedText.defaults = {
top: 0,
left: 0,
scaleX: 1,
scaleY: 1,
angle: 0,
spacing:0,
radius:0,
text: '',
align: 'center',
reverse:'',
fontSize:16,
fontWeight: 'normal',
selectable: true,
fontFamily:'',
fontColor:'black',
textStyleName:''
};
return CurvedText;
})();
This should do it, if I understood you correctly:
Just a small tweak to your updateText() function:
function updateText() {
var original = canvas.getActiveObject();
canvas.remove(original);
setText();
canvas.getActiveObject().set({
angle: original.angle,
top: original.top,
left: original.left,
scaleX: original.scaleX,
scaleY: original.scaleY
}).setCoords();
canvas.renderAll();
}
And finally, here's your all important JSFiddle updated, https://jsfiddle.net/rekrah/pkj82n4b/.
Update (v2)
- since you did plead in your bounty to make your function work, ;-).
Change this line:
this.group = new fabric.Group([], {selectable: this.opts.selectable,name:'arc',radiusVal:this.opts.radius,spacingVal:this.opts.spacing,textFliping:this.opts.reverse,itemName:'text'});
To this:
this.group = new fabric.Group([], {selectable: this.opts.selectable,name:'arc',radiusVal:this.opts.radius,spacingVal:this.opts.spacing,textFliping:this.opts.reverse,itemName:'text',originX:'center',originY:'center'});
And to get it a little more esthetically pleasing you might want to...
Change this line: canvas = new fabric.Canvas('c',);
To this: canvas = new fabric.Canvas('c',{centeredScaling: true});
And here's your original Plunker updated again, https://jsfiddle.net/rekrah/c7cjzkfd/.
This leaves your updateText() function as you had it.
Let me know if you have any other questions. Always happy to help!
You can use this:https://github.com/EffEPi/fabric.curvedText you can see the demo
I am trying to create a class which creates a Crafty entity with specific properties. So far, the functions within the class do not run because 'this' refers to the window object
$(document).ready(function () {
Crafty.init(window.innerWidth, window.innerHeight);
var player = new controller(37,38,39,40);
player.d.color("red").attr({
w: 50,
h: 50,
x: 0,
y: 0
});
// Jump Height = velocity ^ 2 / gravity * 2
// Terminal Velocity = push * (1 / viscosity)
var gravity = 1;
var viscosity = 0.5;
var frame = (1 / 20);
var distanceMultiplier = 10; //pixels per meter
var timeMultiplier = 20; //relative to actual time
var keystart = [];
var keyboard = [];
function controller (controls) {
this.d = Crafty.e();
this.d.addComponent("2D, Canvas, Color, Collision");
this.d.collision();
this.d.mass = 1;
this.d.a = {
extradistance : 0,
velocity : 0,
acceleration : 0,
force : 0,
resistance : 0
};
this.d.a.push = 0;
this.d.v = {
extradistance : 0,
velocity : 0,
acceleration : 0,
force : 0
};
this.d.jumping = true;
this.d.onHit("Collision", function () {
var a = this.d.hit("Collision");
if (a) {
for (var b in a) {
this.d.x = this.d.x - a[b].normal.x * a[b].overlap;
this.d.y = this.d.y - a[b].normal.y * a[b].overlap;
if (a[b].normal.y < -0.5) {
this.d.jumping = false;
}
if (Math.abs(a[b].normal.x) < 0.2) {
this.d.v.velocity = this.d.v.velocity * a[b].normal.y * 0.2;
}
if (Math.abs(a[b].normal.y) < 0.2) {
this.d.a.velocity = this.d.a.velocity * a[b].normal.x * 0.2;
}
}
return;
}
});
this.d.physics = function () {
if (keyboard[arguments[1]] && !this.jumping) {
this.v.velocity = 5;
this.jumping = true;
}
if (keyboard[arguments[1]] && this.jumping) {
var now = new Date();
if (now.getTime() - keystart[arguments[1]].getTime() < 500) {
this.v.velocity = 5;
}
}
if (keyboard[arguments[0]] && keyboard[arguments[2]]) {
this.a.velocity = 0;
} else {
if (keyboard[arguments[0]]) {
this.a.velocity = -3;
}
if (keyboard[arguments[2]]) {
this.a.velocity = 3;
}
}
if (keyboard[arguments[3]]) {
this.v.velocity = -5;
}
this.a.force = this.a.push - this.a.resistance;
this.a.acceleration = this.a.force / this.mass;
this.a.velocity = this.a.velocity + (this.a.acceleration * frame);
this.a.extradistance = (this.a.velocity * frame);
this.a.resistance = this.a.velocity * viscosity;
this.attr({
x: (this.x + (this.a.extradistance * distanceMultiplier))
});
this.v.force = gravity * this.mass;
this.v.acceleration = this.v.force / this.mass;
this.v.velocity = this.v.velocity - (this.v.acceleration * frame);
this.v.extradistance = (this.v.velocity * frame);
this.attr({
y: (this.y - (this.v.extradistance * distanceMultiplier))
});
setTimeout(this.physics, (frame * 1000) / timeMultiplier);
};
this.d.listen = function(){ document.body.addEventListener("keydown", function (code) {
var then = new Date();
if (!keyboard[code.keyCode] && !this.jumping && code.keyCode == arguments[1]) { //only if not yet pressed it will ignore everything until keyup
keyboard[code.keyCode] = true; //start movement
keystart[code.keyCode] = then; //set time
}
if (!keyboard[code.keyCode] && code.keyCode != arguments[1]) { //only if not yet pressed it will ignore everything until keyup
keyboard[code.keyCode] = true; //start movement
keystart[code.keyCode] = then; //set time
}
});
};
}
player.d.physics();
player.d.listen();
document.body.addEventListener("keyup", function (code) {
keyboard[code.keyCode] = false;
});
});
In trying to put the functions as prototypes of the class, I run into a problem.
Crafty.init(500,500);
function block () {
block.d = Crafty.e("2D, Color, Canvas");
block.d.color("red");
block.d.attr({x:0,y:0,h:50,w:50});
}
block.d.prototype.green = function() {
this.color("green");
}
var block1 = new block();
block1.d.color();
If an object is defined in the constructor, I cannot use it to add a prototype to.
Generally in Crafty, we favor composition. That is, you extend an entity by adding more components to it. You can have kind of a hierarchy by having one component automatically add others during init.
I haven't looked through all of your example code, because there's a lot! But consider the second block:
function block () {
block.d = Crafty.e("2D, Color, Canvas");
block.d.color("red");
block.d.attr({x:0,y:0,h:50,w:50});
}
block.d.prototype.green = function() {
this.color("green");
}
var block1 = new block();
block1.d.color();
You're trying to combine Crafty's way of doing things (an entity component system) with classes in a way that's not very idiomatic. Better to do this:
// Define a new component with Crafty.c(), rather than creating a class
Crafty.c("Block", {
// On init, add the correct components and setup the color and dimensions
init: function() {
this.requires("2D, Color, Canvas")
.color("red")
.attr({x:0,y:0,h:50,w:50});
},
// method for changing color
green: function() {
this.color("green");
}
});
// Create an entity with Crafty.e()
block1 = Crafty.e("Block");
// It's not easy being green!
block1.green();
I am reading and trying to do this tutorial:
http://www.sitepoint.com/creating-a-simple-windows-8-game-with-javascript-input-and-sound/
Yesterday, I wrote in this forum with one error, and solved it, but today, I'm in the final one and getting another error.
My default.html file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>CatapultGame</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-light.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<!-- CatapultGame references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
<script src="js/CreateJS/easeljs-0.6.0.min.js"></script>
<script src="js/CreateJS/preloadjs-0.2.0.min.js"></script>
</head>
<body>
<canvas id="gameCanvas"></canvas>
</body>
</html>
and default.js file :
// For an introduction to the Blank template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
WinJS.strictProcessing();
var canvas, context, stage;
var bgImage, p1Image, p2Image, ammoImage, p1Lives, p2Lives, title, endGameImage;
var bgBitmap, p1Bitmap, p2Bitmap, ammoBitmap;
var preload;
// calculate display scale factor - original game assets assume 800x480
var SCALE_X = window.innerWidth / 800;
var SCALE_Y = window.innerHeight / 480;
var MARGIN = 25;
var GROUND_Y = 390 * SCALE_Y;
var LIVES_PER_PLAYER = 3;
var player1Lives = LIVES_PER_PLAYER;
var player2Lives = LIVES_PER_PLAYER;
var isShotFlying = false;
var playerTurn = 1;
var playerFire = false;
var shotVelocity;
var MAX_SHOT_POWER = 10;
var GRAVITY = 0.07;
var isAiming = false;
var aimPower = 1;
var aimStart, aimVector;
var FIRE_SOUND_FILE = "/sounds/CatapultFire.wav";
var HIT_SOUND_FILE = "/sounds/BoulderHit.wav";
var EXPLODE_SOUND_FILE = "/sounds/CatapultExplosion.wav";
var LOSE_SOUND_FILE = "/sounds/Lose.wav";
var AIM_SOUND_FILE = "/sounds/RopeStretch.wav";
var WIN_SOUND_FILE = "/sounds/Win.wav";
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
args.setPromise(WinJS.UI.processAll());
}
};
function initialize() {
canvas = document.getElementById("gameCanvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context = canvas.getContext("2d");
canvas.addEventListener("MSPointerUp", endAim, false);
canvas.addEventListener("MSPointerMove", adjustAim, false);
canvas.addEventListener("MSPointerDown", beginAim, false)
**var stage = new createjs.Stage(canvas);** <<========== HERE IS THE ERROR LINE !!!!
// use preloadJS to get sounds and images loaded before starting
preload = new createjs.PreloadJS();
preload.onComplete = prepareGame;
var manifest = [
{ id: "screenImage", src: "images/Backgrounds/gameplay_screen.png" },
{ id: "redImage", src: "images/Catapults/Red/redIdle/redIdle.png" },
{ id: "blueImage", src: "images/Catapults/Blue/blueIdle/blueIdle.png" },
{ id: "ammoImage", src: "images/Ammo/rock_ammo.png" },
{ id: "winImage", src: "images/Backgrounds/victory.png" },
{ id: "loseImage", src: "images/Backgrounds/defeat.png" },
{ id: "blueFire", src: "images/Catapults/Blue/blueFire/blueCatapultFire.png" },
{ id: "redFire", src: "images/Catapults/Red/redFire/redCatapultFire.png" },
{ id: "hitSound", src: HIT_SOUND_FILE },
{ id: "explodeSound", src: EXPLODE_SOUND_FILE },
{ id: "fireSound", src: FIRE_SOUND_FILE },
{ id: "loseSound", src: LOSE_SOUND_FILE },
{ id: "aimSound", src: AIM_SOUND_FILE },
{ id: "winSound", src: WIN_SOUND_FILE }
];
preload.loadManifest(manifest);
}
function prepareGame()
{
// draw Bg first, others appear on top
bgImage = preload.getResult("screenImage").result;
bgBitmap = new createjs.Bitmap(bgImage);
bgBitmap.scaleX = SCALE_X;
bgBitmap.scaleY = SCALE_Y;
stage.addChild(bgBitmap);
// draw p1 catapult
p1Image = preload.getResult("redImage").result;
p1Bitmap = new createjs.Bitmap(p1Image);
p1Bitmap.scaleX = SCALE_X;
p1Bitmap.scaleY = SCALE_Y;
p1Bitmap.x = MARGIN;
p1Bitmap.y = GROUND_Y - p1Image.height * SCALE_Y;
stage.addChild(p1Bitmap);
// draw p2 catapult and flip
p2Image = preload.getResult("blueImage").result;
p2Bitmap = new createjs.Bitmap(p2Image);
p2Bitmap.regX = p2Image.width;
p2Bitmap.scaleX = -SCALE_X; // flip from right edge
p2Bitmap.scaleY = SCALE_Y;
p2Bitmap.x = canvas.width - MARGIN - (p2Image.width * SCALE_X);
p2Bitmap.y = GROUND_Y - (p2Image.height * SCALE_Y);
stage.addChild(p2Bitmap);
// draw the boulder, and hide for the moment
ammoImage = preload.getResult("ammoImage").result;
ammoBitmap = new createjs.Bitmap(ammoImage);
ammoBitmap.scaleX = SCALE_X;
ammoBitmap.scaleY = SCALE_Y;
ammoBitmap.visible = false; // hide until fired
stage.addChild(ammoBitmap);
// player 1 lives
p1Lives = new createjs.Text("Lives Left : " + player1Lives, "20px sans-serif", "red");
p1Lives.scaleX = SCALE_X;
p1Lives.scaleY = SCALE_Y;
p1Lives.x = MARGIN;
p1Lives.y = MARGIN * SCALE_Y;
stage.addChild(p1Lives);
//player 2 lives
p2Lives = new createjs.Text("Lives Left : " + player2Lives, "20px sans-serif", "red");
p2Lives.scaleX = SCALE_X;
p2Lives.scaleY = SCALE_Y;
p2Lives.x = canvas.width - p2Lives.getMeasuredWidth() * SCALE_X - MARGIN;
p2Lives.y = MARGIN * SCALE_Y;
stage.addChild(p2Lives);
// game title
title = new createjs.Text("Catapult Wars", "30px sans-serif", "black");
title.scaleX = SCALE_X;
title.scaleY = SCALE_Y;
title.x = canvas.width / 2 - (title.getMeasuredWidth() * SCALE_X) / 2
title.y = 30 * SCALE_Y;
stage.addChild(title);
stage.update();
startGame();
}
function startGame()
{
Ticker.setInterval(window.requestAnimationFrame);
Ticker.addListener(gameLoop);
}
function gameLoop()
{
update();
draw();
}
function update() {
if (isShotFlying)
{
// shot in the air
ammoBitmap.x += shotVelocity.x;
ammoBitmap.y += shotVelocity.y;
shotVelocity.y += GRAVITY; //apply gravity to the y(height) values only, obviously
if (ammoBitmap.y + ammoBitmap.image.height >= GROUND_Y ||
ammoBitmap.x <= 0 ||
ammoBitmap.x + ammoBitmap.image.width >= canvas.width)
{
// missed
isShotFlying = false; //stop shot
ammoBitmap.visible = false;
playerTurn = playerTurn % 2 + 1; // invert player ( switch between 1 and 2)
}
else if (playerTurn == 1)
{
if (checkHit(p2Bitmap)) {
// Hit
p2Lives.text = "Lives Left : " + --player2Lives;
processHit();
}
}
else if (playerTurn == 2)
{
if (checkHit(p1Bitmap))
{
// Hit
p1Lives.text = "Lives Left : " + --player1Lives;
processHit();
}
}
}
// No current shot, should either player fire ?
else if (playerTurn == 1)
{
// does the player want to fire ?
if (playerFire)
{
playerFire = false;
ammoBitmap.x = p1Bitmap.x + (p1Bitmap.image.width * SCALE_X / 2);
ammoBitmap.y = p1Bitmap.y;
shotVelocity = aimVector;
fireShot();
}
}
else if (playerTurn == 2)
{
// AI automatically fires (randomly on it's turn)
ammoBitmap.x = p2Bitmap.x + (p2Bitmap.image.width * SCALE_X / 2);
ammoBitmap.y = p2Bitmap.y;
shotVelocity = new createjs.Point(
Math.random() * (-4 * SCALE_X) - 3,
Math.random() * (-3 * SCALE_Y) - 1);
fireShot();
}
}
// triggered by MSPointerDown event
function beginAim(event)
{
if (playerTurn == 1)
{
if (!isAiming)
{
aimStart = new createjs.Point(event.x, event.y);
isAiming = true;
}
}
}
// triggered by MSPointerMove event
function adjustAim(event)
{
if (isAiming)
{
var aimCurrent = new createjs.Point(event.x, event.y);
aimVector = calculateAim(aimStart, aimCurrent);
// TODO write text and/or show aiming arrow on screen
Debug.writeln("Aiming..." + aimVector.x + "/" + aimVector.y);
}
}
// triggered by MSPointerUp event
function endAim(event)
{
if (isAiming) {
isAiming = false;
var aimCurrent = new createjs.Point(event.x, event.y);
aimVector = calculateAim(aimStart, aimCurrent);
playerFire = true;
}
}
function calculateAim(start, end)
{
// this only works for player 1
var aim = new createjs.Point(
(end.x - start.x) / 80,
(end.y - start.y) / 80);
aim.x = Math.min(MAX_SHOT_POWER, aim.x); // cap velocity
aim.x = Math.max(0, aim.x); // fire forward only
aim.y = Math.max(-MAX_SHOT_POWER, aim.y);/// cap velocity
aim.y = Math.min(0, aim.y); // fire up only
return aim;
}
function checkHit(target)
{
// EaselJS hit test doesn't factor in scaling
// so use simple bounding box vs center of rock
// get centre of rock
var shotX = ammoBitmap.x + ammoBitmap.image.width / 2;
var shotY = ammoBitmap.y + ammoBitmap.image.height / 2;
// return wether center of rock is in rectangle bounding target player
return (((shotX > target.x) &&
(shotX <= target.x + (target.image.width * SCALE_X)))
&&
((shotY >= target.y) &&
(shotY <= target.y + (target.image.height * SCALE_Y))));
}
function fireShot()
{
playSound(FIRE_SOUND_FILE);
ammoBitmap.visible = true;
isShotFlying = true;
}
function processHit()
{
playSound(EXPLODE_SOUND_FILE);
isShotFlying = false; // stop shot
ammoBitmap.visible = false; // hide shot
playerTurn = playerTurn % 2 + 1; // change player
if ((player1Lives <= 0) || (player2Lives <= 0)) {
endGame();
}
}
function endGame()
{
Ticker.setPaused(true); // stop game loop
// show win/lose graphic
var endGameImage;
if (player1Lives <= 0)
{
playSound(LOSE_SOUND_FILE);
endGameImage = preload.getResult("loseImage").result;
}
else if (player2Lives <= 0)
{
endGameImage = preload.getResult("winImage").result;
playSound(WIN_SOUND_FILE);
}
var endGameBitmap = new createjs.Bitmap(endGameImage);
stage.addChild(endGameBitmap);
endGameBitmap.x = (canvas.width / 2) - (endGameImage.width * SCALE_X / 2);
endGameBitmap.y = (canvas.height / 2) - (endGameImage.height * SCALE_Y / 2);
endGameBitmap.scaleX = SCALE_X;
endGameBitmap.scaleY = SCALE_Y;
stage.update();
}
function draw() {
// EaselJS allows for easy updates
stage.update();
}
function playSound(path)
{
var sound = document.createElement("audio");
sound.src = path;
sound.autoplay = true;
}
app.oncheckpoint = function (args) {
// TODO: This application is about to be suspended. Save any state
// that needs to persist across suspensions here. You might use the
// WinJS.Application.sessionState object, which is automatically
// saved and restored across suspension. If you need to complete an
// asynchronous operation before your application is suspended, call
// args.setPromise().
};
document.addEventListener("DOMContentLoaded", initialize, false);
app.start();
})();
And there is the problem: when I'm trying to build this game, I'm getting error like this:
0x800a01bd - JavaScript runtime error: Object doesn't support this action
in the marked place in my code
Thanks for any help :)
Three problems, I report them from previous comments:
easeljs not present in the correct folder
mages/Catapults/Red/redFire/redCatapultFire.png missing
change var stage = new createjs.Stage(canvas); to stage = new createjs.Stage(canvas); you don't have to instantiate that variable as reported in the tutorial (http://www.sitepoint.com/creating-a-simple-windows-8-game-with-javascript-game-basics-createjseaseljs/)
And close this other related question (https://stackoverflow.com/a/16101960/975520), I think is solved by posting your solution as answer.