i'm creating a browser game which is meant to be played as a hologram.
The screen should be displaying something like this:
https://www.youtube.com/watch?v=Y60mfBvXCj8
Therefore i thought i have to create 4 canvas (no problem), but three of them should only display whats happening on the first.
I've tried to let it draw an Image of the canvas and let it display to the other canvas.
Any help would be appreciated!
The game is created with Box2D.
edit:
i want the space ship to be drawn in every canvas, but only controlled in one.
my code: http://s000.tinyupload.com/index.php?file_id=68837773176112789787
the problem is, that its only displaying on one canvas!
what i've put in the HTML:
<canvas id="canvas1" width="500" height="500"></canvas>
<canvas id="canvas2" width="500" height="500"></canvas>
<canvas id="canvas3" width="500" height="500"></canvas>
<canvas id="canvas4" width="500" height="500"></canvas>
what is meant to print it to the others:
JS
var sourceCtx, destinationCtx, imageData;
//get the context of each canvas
sourceCtx = canvas2.getContext('2d');
canvas2Ctx = canvas3.getContext('2d');
//copy the data
imageData = sourceCtx.getImageData(0, 0, canvas2.width - 1, canvas2.height - 1);
//apply the image data
canvas3Ctx.putImageData(imageData, 0, 0);
//done
Holographic pyramid display
How to render for a pyramid reflecting display.
To do this use a single display canvas in the HTML and a canvas stored in memory for rendering.
Mirrored render canvas
The rendering canvas is clipped to a triangle to prevent pixels overlapping and the transform is mirrored so that the final effect is correctly seen. Eg text is back to front.
The offscreen rendering canvas is then rendered to the display canvas, starting at the top and making a total of 4 copies each rotated 90deg.
The rendering canvas width will be the minimum of the display width or height and half that for the height in order to fit the display.
Needs fullscreen mode
For the FX to work you will need to enter fullscreen mode. I have not included how this is done but I am sure there is a QA on stackoverflow that will step you through the process.
Dead zone
At the center of the display is a area on which the pyramid will rest (I call it the dead zone) As many of these displays are homemade the size of the dead zone will vary. In the very first line of the demo below is a constant deadZoneSize that will set the dead zone size. It is currently set at 0.1 which is 10% of the view size. You may need to adjust this value to suit your particular reflecting display.
Example code
The code example is full of comments in the relevant parts. It will create and setup the display canvas and render canvas. Create the clip area and set up the mirrored rendering transform, so you can render as normal. A mainLoop function will call a function called renderContent with the first argument as being the context of the render canvas. Just render your content as normal (use size and hSize for the width and height of the visible render area (maybe I should have used a better name))
The demo includes an example rendering just for the fun of it, that is all at the bottom and has minimum comments as not really relevant to the question.
const deadZoneSize = 0.1; // As fraction of fitted box size
// for FX em and em4 are just custom unit size and 1/4 size
var em,em4;
// to fit all four views use the min width or height
var size = Math.min(innerWidth,innerHeight);
// half size
var hSize = size / 2 | 0;
// there is a small area where nothing should be displayed.
// This will depend on the pyrimide being used.
var deadZone = size * 0.1 | 0; // about 10% of view area
// Display canvas d for display
const dCanvas = document.createElement("canvas");
// Render canvas
const rCanvas = document.createElement("canvas");
// get rendering context for both
const dCtx = dCanvas.getContext("2d");
const rCtx = rCanvas.getContext("2d");
// Set the display canvas to fill the page
Object.assign(dCanvas.style,{
position : "absolute",
zIndex : 10, // place above
top : "0px",
left : "0px",
background : "black",
})
// add the display canvas to the DOM
document.body.appendChild(dCanvas);
//Size function resizes canvases when needed
function resize(){
startTime = undefined;
size = Math.min(innerWidth,innerHeight);
hSize = size / 2 | 0;
deadZone = size * deadZoneSize | 0; // about 10% of view area
dCanvas.width = innerWidth;
dCanvas.height = innerHeight;
rCanvas.width = size;
rCanvas.height = hSize; // half height
em = size * 0.1 | 0; // define our own unit size
em4 = Math.max(1,em * 0.25 | 0); // define quarter unit size min of 1
}
// To ensure pixels do not stray outside the view area and overlap use a clip on the render canvas
// ctx the context to appy the clip path to
function defineClip(ctx){
ctx.beginPath();
ctx.lineTo(0,0);
ctx.lineTo(size,0);
ctx.lineTo(hSize + deadZone, hSize - deadZone);
ctx.lineTo(hSize - deadZone, hSize - deadZone);
ctx.clip();
// The rendering is mirrored from the holo pyramid
// to avoid seeing text mirrored you need to mirror the
// rendering transform
ctx.setTransform(-1,0,0,1,size,0); // x axis from right to left, origin at top right
}
// Copying the rendered canvas to the display canvas
// ctx is the display canvas context
// image is the rendered canvas
function display(ctx,image) {
// for each face of the pyramid render a view
// Each image is just rotated 90 deg
// first clear the canvas
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
// top
// use the center of the display canvas as the origin
ctx.setTransform(1,0,0,1,ctx.canvas.width / 2 | 0, ctx.canvas.height / 2 | 0);
// draw the image
ctx.drawImage(image,-hSize,-hSize);
// Right
ctx.transform(0,1,-1,0,0,0); // rotate 90 deg. This is better than ctx.rotate as it can have slight
// problems due to floating point errors if not done correctly
ctx.drawImage(image,-hSize,-hSize);
// bottom
ctx.transform(0,1,-1,0,0,0);
ctx.drawImage(image,-hSize,-hSize);
// left
ctx.transform(0,1,-1,0,0,0);
ctx.drawImage(image,-hSize,-hSize);
// restore the default transform;
ctx.setTransform(1,0,0,1,0,0);
}
// the main render loop
var globalTime;
var startTime;
function mainLoop(time){
// check canvas size. If not matching page then resize
if(dCanvas.width !== innerWidth || dCanvas.height !== innerHeight) {
resize();
}
if(startTime === undefined){ startTime = time }
globalTime = time - startTime;
// clear the render canvas ready for next render
rCtx.setTransform(1,0,0,1,0,0); // reset transform
rCtx.globalAlpha = 1; // reset alpha
rCtx.clearRect(0,0,size,hSize);
// save the context state so that the clip can be removed
rCtx.save();
defineClip(rCtx); // set the clip
renderContent(rCtx); // call the rendering function
// restore the context state which removes the clip
rCtx.restore();
// rendering is ready for display so render the holo view
// on to the display canvas's context
display(dCtx, rCanvas);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
//=====================================================================================================
// The following is just something interesting to display and is not directly related to the answer
//=====================================================================================================
// The main rendering function
// This is where you render your content. It can be anything from a game to just plain old text
// You can even use a video element and display a video.
// The rendering context is already set up to correctly mirror the content so just render everything as normal
const randG = (min, max , p = 2) => (max + min) / 2 + (Math.pow(Math.random(), p) * (max - min) * 0.5) * (Math.random() < 0.5 ? 1 : -1);
const bootUp = ["Power On",1,1000,"Sub system test",0.5, 3000, "Calibrating scanner",0.5, 6000, "Welcome",1,8000];
function noisyText(ctx){
var textTime = globalTime / 8000; // 8 second boot up
if(screenFlashDone){
if(globalTime > screenFlashes[0]) { // play screen flash seq
screenFlashes.shift();
screenFlash(ctx,true,screenFlashes.shift(),screenFlashes.shift());
}
}else{
screenFlash(ctx);
}
ctx.font = ((bootUp[1] * em) | 0) + "px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "center";
var tx = randG(-em4 * 4, em4 * 4, 64); // G for kind of a bit like gausian. Last num controls distrubution
var ty = randG(-em4 * 4, em4 * 4, 64);
var xx = size / 2 + tx;
var yy = em * 2 + ty;
ctx.fillStyle = `hsl(${randG(160,250,32)|0},100%,50%)`;
if(bootUp[2] < globalTime){
bootUp.shift();
bootUp.shift();
bootUp.shift();
}
ctx.fillText(bootUp[0], xx, yy);
ctx.save(); // need the normal non mirror transform for the noise FX
ctx.setTransform(1,0,0,1,0,0);
for(var y = -em/1.2|0; y < em/2; y += 1){
if((yy+y) % 3 === 0){
ctx.clearRect(0,yy+y,size,1); // give scan line look
}else{
if(Math.random() < 0.1){ // only on 10% of lines.
ctx.drawImage(ctx.canvas,0,yy + y, size, 2,randG(-em4 * 4,em4 * 4,32),yy + y, size, 2);
}
}
}
ctx.fillRect(0,((globalTime / 4000) * hSize)%hSize,size,2);
ctx.filter = `blur(${randG(em4/2,em4,2)|0}px)`;
ctx.drawImage(ctx.canvas,0,0);
ctx.restore();
}
const screenFlashes = [0,500,3,1000,200,2,4000,100,3,6000,100,1,7500,50,1,7800,50,1, 9000];
var screenFlashStart;
var screenFlashLen;
var screenFlashDone = true;
var screenFlashLayers = 1;
function screenFlash(ctx,start,length,layers){
if(start){
screenFlashStart = globalTime;
screenFlashLen = length;
screenFlashDone = false;
screenFlashLayers = layers;
}
var normTime = (globalTime - screenFlashStart) / screenFlashLen;
if(normTime >= 1){
screenFlashDone = true;
normTime = 1;
}
for(var i = 0; i < screenFlashLayers; i++){
var tx = randG(-em4 * 4, em4 * 4, 64); // G for kind of a bit like gausian. Last num controls distrubution
var ty = randG(-em4 * 4, em4 * 4, 64);
ctx.globalAlpha = (1-normTime) * Math.random();
ctx.fillStyle = `hsl(${randG(160,250,32)|0},100%,50%)`;
ctx.fillRect(tx,ty,size,hSize);
}
ctx.globalAlpha = 1;
}
function randomBlur(ctx) {
ctx.save(); // need the normal non mirror transform for the noise FX
ctx.filter = `blur(${randG(em4/2,em4,2)|0}px)`;
ctx.drawImage(ctx.canvas,0,0);
ctx.restore();
}
function ready(ctx) {
ctx.fillStyle = "#0F0";
ctx.font = em + "px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "center";
ctx.fillText("Holographic",hSize,em);
ctx.font = em/2 + "px monospace";
ctx.fillText("display ready.",hSize,em * 2);
// draw edges
ctx.strokeStyle = "#0F0";
ctx.lineWidth = em4;
ctx.beginPath();
ctx.lineTo(0,0);
ctx.lineTo(size,0);
ctx.lineTo(hSize + deadZone, hSize - deadZone);
ctx.lineTo(hSize - deadZone, hSize - deadZone);
ctx.closePath();
ctx.stroke();
}
function renderContent(ctx){
// all rendering is mirrored, but the transform takes care of that for you
// just render as normal. Remember you can only see the
// triangular area with the wide part at the top
// and narrow at the bottom.
// Anything below hSize - deadZone will also not appear
if(globalTime < 8000){
noisyText(ctx);
randomBlur(ctx);
}else{
ready(ctx);
}
randomBlur(ctx);
}
A quick side note. I feel your question meets the SO requirements and is not off topic, nor are you asking for someone to write the code. You have shown that you have put some effort into research. This question will be of interest to others. I hope this answer helps, good luck in your project and welcome to SO.
I've been working on a specific animation in which I need to convert(with animation) a Rounded Rectangle Shape to Circle. I've checked the documentation of paper.js and haven't found any predefined function to achieve this.
-->
The animation needs to be smooth. As the number of rectangles I'm working with is very high, I can't use the "remove current rounded rect and redraw one more rounded version" method. It reduces the performace and the animation gets laggy.
This is the code I'm using to generate rounded rectangle.
// Had to paste something to post the question
// Though the whole code can be seen on codepen link
var rect = new Rectangle();
var radius = 100, origin = {x: 100, y: 100};
rect.size = new Size(radius, radius);
rect.center = new Point(origin.x, origin.y);
var cornerSize = radius / 4;
var shape = new Path.Rectangle(rect, cornerSize);
Prepared this Codepen example to show the progress.
If we can work out the whole animation using any other object types, that will be fine too. For now I can't find any any property which can transform the rounded rectangle to circle.
I'm also animating color of the object and position. I've gone through many documents to find out color animation.
PS: If there is any other(better) technique to animate colors of object, please share that too.
You will first have to create a path as a rounded rectangle. Then with each step in your animation you have to modify the eight segments of the path. This will only work with Path objects, not if your rectangle is a Shape.
The segment points and the handles have to be set like this:
κ (kappa) is defined in paper.js as Numerical.KAPPA (more on Kappa here).
The code to change the radius could look like this (Click here for the Sketch):
var rect = new Path.Rectangle(new Point(100, 100), new Size(100, 100), 30);
rect.fullySelected = true;
var step = 1;
var percentage = 0;
function onFrame(event) {
percentage += step;
setCornerRadius(rect, percentage)
if (percentage > 50 || percentage < 0) {
step *= -1;
}
}
function setCornerRadius(rectPath, roundingPercent) {
roundingPercent = Math.min(50, Math.max(0, roundingPercent));
var rectBounds = rectPath.bounds;
var radius = roundingPercent/100 * Math.min(rectBounds.width, rectBounds.height);
var handleLength = radius * Numerical.KAPPA;
l = rectBounds.getLeft(),
t = rectBounds.getTop(),
r = rectBounds.getRight(),
b = rectBounds.getBottom();
var segs = rectPath.segments;
segs[0].point.x = segs[3].point.x = l + radius;
segs[0].handleOut.x = segs[3].handleIn.x = -handleLength;
segs[4].point.x = segs[7].point.x = r - radius;
segs[4].handleOut.x = segs[7].handleIn.x = handleLength;
segs[1].point.y = segs[6].point.y = b - radius;
segs[1].handleIn.y = segs[6].handleOut.y = handleLength;
segs[2].point.y = segs[5].point.y = t + radius;
segs[2].handleOut.y = segs[5].handleIn.y = -handleLength;
}
Edit: I just found a much easier way using a shape. Not sure which approach performs faster.
Here is the implementation using a Shape (Click here for the Sketch).
var size = 100;
var rect = new Shape.Rectangle(new Rectangle(new Point(100, 100), new Size(size, size)), 30);
rect.strokeColor = "red";
var step = 1;
var percentage = 0;
function onFrame(event) {
percentage = Math.min(50, Math.max(0, percentage + step));
rect.radius = size * percentage / 100;
if (percentage >= 50 || percentage <= 0) {
step *= -1;
}
}
Change the corner size to the following
var cornerSize = circle.radius / 1;
For a project I made something in chartjs that looked something to this: https://jsfiddle.net/ethernetz/e50fn31m/2/
I had to do some digging around on the internet to get a number to show up in the middle of the doughnut graph, but that was exactly what I wanted. Then I wanted to make a second doughnut graph, which made the project looks something like this: https://jsfiddle.net/ethernetz/4uw1ksu1/
As you can see, the "60" from the completion graph shows up on top of the "40" from the incompletion graph and vice versa.
I suspect it has to do with me never specifying what graph I want to edit here:
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
...but I don't know how to fix it. Any help would be appreciated.
Change the following line of code in your plugin :
var text = 60;
to this :
var text = chart.id == 0 ? 60 : 40;
basically, when a chart is created, it is assigned an id (0 based). so you need to set the text based on that chart-id.
also, there's no need to add two separate plugins, as you are creating a global plugin, which will be applied to all of your charts.
Here is the working example on JSFiddle
use only one "chart.pluginService.register" for all charts, because it subscribes to the chart class, so mychart and mychart2 belong to chart class, then don't use the callback "chart"... use mychart and mychart2 instead of callback to recognize each chart registered.
new code:
var charts = [{
current: myChart
, text: 60
}, {
current: myChart2
, text: 40
}];
Chart.pluginService.register({
beforeDraw: function(chart) {
for (var iterator of charts) {
var width = iterator.current.chart.width,
height = iterator.current.chart.height,
ctx = iterator.current.chart.ctx;
//ctx.clearRect(0, 0, width, height);
//ctx.restore(); dont clear, this delete the previous chart, drawing only text
var fontSize = (height / 114).toFixed(2);
ctx.font = fontSize + "em lato";
ctx.fillStyle = "rgba(0,0,0, 0.85)"
ctx.textBaseline = "middle";
var text = iterator.text,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2.5;
ctx.fillText(text, textX, textY);
ctx.save();
}
}
});
updated jsfiddle: https://jsfiddle.net/4uw1ksu1/2/
I use this code to resize my canvas width and height to the viewport of the browser
function scaleCanvas(){
c.width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
c.height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
drawMenu();
}
It works really great but now I want to put the Coordinates of my objects text etc in relations to the size of the canvas I tried this
// Canvas grösse
c.width = 1280;
c.height = 720;
// Text Schwer
var schwerx = 890;
var schwery = 52;
var schwerw = 100;
var schwerh = 30;
var schwerf = 22;
// Basis Höhe und Breite
var basex = 1280;
var basey = 720;
// Function Schwer
function schwer(){
var rx = schwerx / basex;
var x = rx * c.width;
var ry = schwery / basey;
var y = ry * c.height;
var rw = schwerw / basex;
var w = rw * c.width;
var rh = schwerh / basey;
var h = rh * c.height;
ctx.save();
ctx.rotate(16.3*Math.PI/180);
ctx.font = getFont();
ctx.fillStyle = "#feec47";
ctx.fillText('SCHWER', x, y, w, h);
ctx.restore();
function getFont() {
var ratio = schwerf / basex;
var size = c.width * ratio;
return (size|0) + 'px Pokemon';
}
}
This works great for the font size and on some Width and Heights of the canvas but not on all scales.
You need to start with a base size which is used for calculations, say 1280x720 as in your code.
From there you need two factors, one for horizontal scaling and one for vertical.
Normalize current size using your base size - this will be the scale factors you use to scale the two dimensions:
var factorX = newWidth / baseWidth;
var factorY = newHeight / baseHeight;
You can now scale using one of two approaches: either scale transforming the scale of the context:
ctx.setTransform(factorX, 0, 0, factorY, 0, 0);
or generate new temporary points of the existing points:
var schwerx = 890;
var schwery = 52;
var nSchwerx *= factorX;
var nSchwery *= factorY;
etc...
If can be convenient to store these coordinates in an object or array so they can be processed using a for-loop.
In the case of fonts you will probably use transformation - it's possible to approximate the size without - here is one method which can be used with a bounding box to define the area the text should fit (predefined using the base size and the text/base font. A method to get the height of the font is shown in the same link).
Pros and cons
There are pros and cons with both: transformation matrix makes it easy to scale to any size without doing much with existing coordinates. However, it will also interpolate/resample the graphics so with large factors things will start to look blurry, text may look squeezed etc.
Scaling coordinates (path) will obtain full quality but requires a few more steps as well as being to maintain the original coordinates. It's also more complicated to get text to fit as its size is based on height and width will follow. Width may also be non-linear relative to height depending on the typeface and how it is optimized at the height it's being used, and will therefor require special treatment.
Too much code to paste here so....
Please see JS Fiddle at http://jsfiddle.net/1a2s35m2/ for current code.
I am attempting to create a horizontal bar chart using flot. As you will see from the Fiddle, this works fine but I want to display the values of the bars within the bars themselves and not as labels in the Y Axis, as in the picture below...
I have attempted to use the "labels" plugin and also the barnumbers plugin but these don't seem to work. (Barnumbers comes close but displays 0 1 2 3 as the values.
Any ideas?
I'm really starting to sound like a broken record on here, but as your charts become really complicated forget the plugins and do it yourself.
Here's modified code from my links above catered for how you drew your plots:
// after initial plot draw, then loop the data, add the labels
// I'm drawing these directly on the canvas, NO HTML DIVS!
// code is un-necessarily verbose for demonstration purposes
var ctx = somePlot.getCanvas().getContext("2d"); // get the context
var allSeries = somePlot.getData(); // get your series data
var xaxis = somePlot.getXAxes()[0]; // xAxis
var yaxis = somePlot.getYAxes()[0]; // yAxis
var offset = somePlot.getPlotOffset(); // plots offset
ctx.font = "12px 'Segoe UI'"; // set a pretty label font
ctx.fillStyle = "black";
for (var i = 0; i < allSeries.length; i++){
var series = allSeries[i];
var dataPoint = series.datapoints.points; // one point per series
var x = dataPoint[0];
var y = dataPoint[1];
var text = x + '%';
var metrics = ctx.measureText(text);
var xPos = xaxis.p2c(x)+offset.left - metrics.width; // place at end of bar
var yPos = yaxis.p2c(y) + offset.top - 2;
ctx.fillText(text, xPos, yPos);
}
Updated fiddle.