Does an HTML5 canvas always have to be a rectangle? - javascript

The reason I'm interested in canvases having any shape is that it would then be possible to cut out images with Bezier curves and have the text of a web page flow around the canvas shape, i.e. cut-out image.
What is needed is the possibility to have a free-form shaped div, SVG and HTML5 canvas. (Applied to SVG, I understand this would be equivalent to Flash symbols.) You could then imagine applying a box model (padding, border and margin) for shapes, but it wouldn't be a box (it would be parallel to the shape)!
I suppose it would also then be possible to have text that wraps inside a shape as much as text that flows around a shape.
I read an interesting blog post about "Creating Non-Rectangular Layouts with CSS Shapes" here: http://sarasoueidan.com/blog/css-shapes/
but it doesn't include text wrapping inside a shape.
Then, there's also a CSS Shapes editor for Brackets (a code editor):
http://blogs.adobe.com/webplatform/2014/04/17/css-shapes-editor-in-brackets/

As simple as it may sound it actually involves quite a few steps to achieve.
An outline would look something like this:
Define the shape as a polygon, ie. point array
Find bounds of polygon (the region the polygon fits inside)
Contract polygon with padding using either a cetronid algorithm or simply a brute-force approach using center of bounds
Define line height of text and use that as a basis for number of scan-lines
Basically use a polygon-fill algorithm to find segment within the shape which can fill in text. The steps for this is:
Use an odd/even scanner by getting an intersection point (using line intersection math) with text scan line and each of the lines between the points in the polygon
Sort the points by x
use odd and even point to create a segment. This segment will always be inside the polygon
Add clipping using original polygon
Draw in image
Use the segments to get a width. Start parsing the text to fill and measure the width.
When text width fits within the segment width then print the chars that fits
Repeat for next text/words/chars until end of text or segments
In other words: you would need to implement a polygon fill algorithm but instead of filling in lines (per pixel line) you use the line as basis for the text.
This is fully doable; actually, I went ahead to create a challenge for myself on this problem, for the fun of it, so I created a generic solution that I put on GitHub released under MIT license.
The principle described above are implemented, and to visualize the steps:
Define the polygon and padding - here I chose to just use a simple brute-force and calculate a smaller polygon based on center and a padding value - the light grey is the original polygon and the black obviously the contracted version:
The points are defined as an array [x1, y1, x2, y2, ... xn, yn] and the code to contract it (see link to project for full source on all these parts):
var pPoints = [],
i = 0, x, y, a, d, dx, dy;
for(; i < points.length; i += 2) {
x = points[i];
y = points[i+1];
dx = x - bounds.px;
dy = y - bounds.py;
a = Math.atan2(dy, dx);
d = Math.sqrt(dx*dx + dy*dy) - padding;
pPoints.push(bounds.px + d * Math.cos(a),
bounds.py + d * Math.sin(a));
}
Next step is to define the lines we want to scan. The lines are based on line height for font:
That is simple enough - just make sure the start and end points are outside the polygon.
We use an odd/even scan approach and check intersection of the scanline versus all lines in the polygon. If we get a intersect point we store that in a list for that line.
The code to detect intersecting lines is:
function getIntersection(line1, line2) {
// "unroll" the objects
var p0x = line1.x1,
p0y = line1.y1,
p1x = line1.x2,
p1y = line1.y2,
p2x = line2.x1,
p2y = line2.y1,
p3x = line2.x2,
p3y = line2.y2,
// calc difference between the coords
d1x = p1x - p0x,
d1y = p1y - p0y,
d2x = p3x - p2x,
d2y = p3y - p2y,
// determinator
d = d1x * d2y - d2x * d1y,
px, py,
s, t;
// if is not intersecting/is parallel then return immediately
if (Math.abs(d) < 1e-14)
return null;
// solve x and y for intersecting point
px = p0x - p2x;
py = p0y - p2y;
s = (d1x * py - d1y * px) / d;
if (s >= 0 && s <= 1) {
// if s was in range, calc t
t = (d2x * py - d2y * px) / d;
if (t >= 0 && t <= 1) {
return {x: p0x + (t * d1x),
y: p0y + (t * d1y)}
}
}
return null;
}
Then we sort the point for each line and use pairs of points to create segments - this is actually a polygon-fill algorithm. The result will be:
The code to build segments is a bit extensive for this post so check out the project linked above.
And finally we use those segments to replace with actual text. We need to scan a text from current text pointer and see how much will fit inside the segment width. The current code is somewhat basic and skips a lot of considerations such as word breaks, text base-line position and so forth, but for initial use it will do.
The result when put together will be:
Hope this gives an idea about the steps involved.

Related

How to render raycasted wall textures on HTML-Canvas

I am trying to build a raycasting-engine. i have successfully rendered a scene column by column using ctx.fillRect() as follows.
canvas-raycasting.png
demo
code
the code i wrote for above render:
var scene = [];// this contains distance of wall from player for an perticular ray
var points = [];// this contains point at which ray hits the wall
/*
i have a Raycaster class which does all math required for ray casting and returns
an object which contains two arrays
1 > scene : array of numbers representing distance of wall from player.
2 > points : contains objects of type { x , y } representing point where ray hits the wall.
*/
var data = raycaster.cast(wall);
/*
raycaster : instance of Raycaster class ,
walls : array of boundries constains object of type { x1 , y1 , x2 , y2 } where
(x1,y1) represent start point ,
(x2,y2) represent end point.
*/
scene = data.scene;
var scene_width = 800;
var scene_height = 400;
var w = scene_width / scene.length;
for(var i=0;i<scene.length;++i){
var c = scene[i] == Infinity ? 500 : scene[i] ;
var s = map(c,0,500,255,0);// how dark/bright the wall should be
var h = map(c,0,500,scene_height,10); // relative height of wall (farther the smaller)
ctx.beginPath();
ctx.fillStyle = 'rgb('+s+','+s+','+s+')';
ctx.fillRect(i*w,200-h*0.5,w+1,h);
ctx.closePath();
}
Now i am trying to build an web based FPS(First Person Shooter) and stucked on rendering wall-textures on canvas.
ctx.drawImage() mehthod takes arguments as follows
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
ctx.drawImage_arguments
but ctx.drawImage() method draws image as a rectangle with no 3D effect like Wolfenstein 3D
i have no idea how to do it.
should i use ctx.tranform()? if yes, How ? if no, What should i do?
I am looking for the Maths used to produce pseudo 3d effect using 2D raycasting.
some pseudo 3d games are Wolfenstein 3D Doom
i am trying to build something like this
THANK YOU : )
The way that you're mapping (or not, as the case may be) texture coordinates isn't working as intended.
I am looking for the Maths used to produce pseudo 3d effect using 2D raycasting
The Wikipedia Entry for Texture Mapping has a nice section with the specific maths of how Doom performs texture mapping. From the entry:
The Doom engine restricted the world to vertical walls and horizontal floors/ceilings, with a camera that could only rotate about the vertical axis. This meant the walls would be a constant depth coordinate along a vertical line and the floors/ceilings would have a constant depth along a horizontal line. A fast affine mapping could be used along those lines because it would be correct.
A "fast affine mapping" is just a simple 2D interpolation of texture coordinates, and would be an appropriate operation for what you're attempting. A limitation of the Doom engine was also that
Doom renders vertical and horizontal spans with affine texture mapping, and is therefore unable to draw ramped floors or slanted walls.
It doesn't appear that your logic contains any code for transforming coordinates between various coordinate spaces. You'll need to apply transforms between a given raytraced coordinate and texture coordinate spaces in the very least. This typically involves matrix math and is very common and can also be referred to as Projection, as in projecting points from one space/surface to another. With affine transformations you can avoid using matrices in favor of linear interpolation.
The coordinate equation for this adapted to your variables (see above) might look like the following:
u = (1 - a) * wallStart + a * wallEnd
where 0 <= *a* <= 1
Alternatively, you could use a Weak Perspective projection, since you have much of the data already computed. From wikipedia again:
To determine which screen x-coordinate corresponds to a point at
A_x_,A_z_
multiply the point coordinates by:
B_x = A_x * B_z / A_z
where
B_x
is the screen x coordinate
A_x
is the model x coordinate
B_z
is the focal length—the axial distance from the camera center *to the image plane*
A_z
is the subject distance.
Because the camera is in 3D, the same works for the screen y-coordinate, substituting y for x in the above diagram and equation.
In your case A_x is the location of the wall, in worldspace. B_z is the focal length, which will be 1. A_z is the distance you calculated using the ray trace. The result is the x or y coordinate representing a translation to viewspace.
The main draw routine for W3D documents the techniques used to raytrace and transform coordinates for rendering the game. The code is quite readable even if you're not familiar with C/ASM and is a great way to learn more about your topics of interest. For more reading, I would suggest performing a search in your engine of choice for things like "matrix transformation of coordinates for texture mapping", or search the GameDev SE site for similar.
A specific area of that file to zero-in on would be this section starting ln 267:
> ========================
> =
> = TransformTile
> =
> = Takes paramaters:
> = tx,ty : tile the object is centered in
> =
> = globals:
> = viewx,viewy : point of view
> = viewcos,viewsin : sin/cos of viewangle
> = scale : conversion from global value to screen value
> =
> = sets:
> = screenx,transx,transy,screenheight: projected edge location and size
> =
> = Returns true if the tile is withing getting distance
> =
A great book on "teh Maths" is this one - I would highly recommend it for anyone seeking to create or improve upon these skills.
Update:
Essentially, you'll be mapping pixels (points) from the image onto points on your rectangular wall-tile, as reported by the ray trace.
Pseudo(ish)-code:
var image = getImage(someImage); // get the image however you want. make sure it finishes loading before drawing
var iWidth = image.width, iHeight = image.height;
var sX = 0, sY = 0; // top-left corner of image. Adjust when using e.g., sprite sheets
for(var i=0;i<scene.length;++i){
var c = scene[i] == Infinity ? 500 : scene[i];
var s = map(c,0,500,255,0);// how dark/bright the wall should be
var h = map(c,0,500,scene_height,10); // relative height of wall (farther the smaller)
var wX = i*w, wY = 200 - h * 0.5;
var wWidth = w + 1, wHeight = h;
//... render the rectangle shape
/* we are using the same image, but we are scaling it to the size of the rectangle
and placing it at the same location as the wall.
*/
var u, v, uW, vH; // texture x- and y- values and sizes. compute these.
ctx.drawImage(image, sX, sY, iWidth, iHeight, u, v, uW, vH);
}
Since I'm not familiar with your code performing the raytrace, its' coordinate system, etc, you may need to further adjust the values for wX, wY, wWidth, and wHeight (e.g., translate points from center to top-left corner).

Canvas - How to find if a point is above or under a diagonale line?

In a 2D game, i need to find if an object is above or under a diagonale line.
Anyone knows how to do this ?
(i use createJS framework)
OK, scrap my previous answer and use line intersection instead. Shoot a line from the point to test straight up. If there is an intersection the point is below, if none, the point is either above or to the side of the line.
To avoid side cases (no pun), extend the original line using interpolation.
Here is a function to do line intersection. To do linear interpolation of the original line simply use some extreme values:
var tx1 = x1 + (x2-x1) * -51000;
var ty1 = y1 + (y2-y1) * -51000;
var tx2 = x1 + (x2-x1) * 53200;
var ty2 = y1 + (y2-y1) * 53200;
Update I was a bit in a hurry this morning so here's a small update. As blindman67 points out, you can use just the d in the linked intersection function and check s/t if they are in the normalized range (or just use cross product - see his answer it that is a better fit).
Build a triangle using the upper coordinates to create a shape. For example, if your line look like:
You can create a shape of if using x2 and y1:
Now simply add the triangle to the path and do a isPointInPath(x, y), if true it's above, if false it's below.
If you need to check below reverse the process.
(wowa! a lot of arrows there... but you'll get the idea :) )
Edge cases (pun intended): if point is very close to one of the ends -> just extend the line, or make polygon extending (x1,y1) up to edge of the area.
Actually, thinking about it: triangles may not be so suitable, rather, use the upper edge of the canvas as a segment of polygon, then the next segment would be vertical line down to the end of the diagonal line, the the final segment from the beginning of the diagonal line to the upper left side of the canvas. I'm just too lazy to redo the graphics but you get the idea..
Use the cross product of the point and the line.
You need to move the whole coord system to the start of the line and then get the cross product of the line and the point. If the result is negative then the point is left of the line, if positive then the point is right of the line, if zero then the point is on the line.
// the point
var px = 100;
var py = 100;
// the line
var lx1 = 20;
var ly1 = 20;
var lx2 = 320;
var ly2 = 120;
// move line end and point so that line start is at 0,0
lx2 -= lx1;
ly2 -= ly1;
px -= lx1;
py -= ly1;
// get cross product
var cross = lx2 * py - ly2 * px;
if(cross < 0){ // point is to the left (anticlockwise)
}else if(cross > 0){ // point is to the right (clockwise)
}else{ // cross must be zero then point is on the line
}

How to convert VML path to SVG path?

I am currently working on an app for which I need to convert VML shapes into SVG shapes. While I can handle all other aspects of it, I am facing problem in correctly converting the path of the shape from VML path to SVG path. I am using a combination of XSLT and Javascript for my codes.
I have enough control on conversion of angular shapes (i.e. shapes containing only straight lines) but I am facing difficulty in converting path with curves.
For instance, for a simple shape this:
The VML path is: m10800,qx21600,10800,10800,21600l,21600,,xe
Now if I replace m with M, l with L and qx with Q and do the necessary scaling of the coordinates I get the following SVG shape:
The SVG path treats first set of coordinates in Q/qx as a control point and hence the actual path doesn't passes through the point whereas the VML intended those coordinates as the point over which the path should pass through. I don't understand how I can achieve that with SVG (i.e. making sure that the path passes through a specific point or points).
Currently I am using this and this for researching SVG and VML respectively. I also tried using Vector Converter 1.2 but that doesn't works either.
Can anyone suggest me a way, a library, any study links or tutorials where I can find a solution to my problem?
Thanks in advance!!
"qx" in VML is an "elliptical quadrant", "Q" in SVG is a quadratic bezier. Completely different things.
The simplest solution to converting a "qx" is to approximating it with a cubic bezier. Using an arc would be most accurate, but there will be some tricky maths involved in order to determine the correct value for "sweep flag". Although a cubic bezier is not a perfect approximation to a quadrant, it is very close, and the error will not be noticeable enough to affect your drawings.
The secret to drawing circular/elliptical quadrants is the constant 0.5522847498. It defines how long the control point lines have to be to simulate the elliptical curve. You can find explanations for how it is derived by googling that number.
So VML defines "qx" as an elliptical quadrant starting out in the X direction. So given the path command "qx21600,10800", the algorithm for conversion will be:
arcFactor = 0.5522847498;
currentX = 10800;
currentY = 0; // start coords (from the move)
x = 21600;
y = 10800; // first coords in "qx"
dx = x - currentX;
dy = y - currentY;
// Calculate first control point
cp1x = currentX + dx * arcFactor;
cp1y = currentY; // starts out horizontal
// Calculate second control point
cp2x = x;
cp2y = y - dy * arcFactor;
svgBezier = "C" + cp1x + "," + cp1y + "," + cp2x + "," + cp2y + "," + x + "," + y;
Now your curve has a second set of coordinates to the qx. The spec says that it means a repeat of the "qx" command. However it makes no sense for the second set to behave exactly the same as the qx (ie. start out horizontal). So I think they must actually behave as a "qy" (start out vertical). Ie. the qx and qy alternate. Assuming that is the case, the calculation for the qy should be:
// Calculate first control point
cp1x = currentX; // starts out vertical
cp1y = currentY + dy * arcFactor;
// Calculate second control point
cp2x = x - dx * arcFactor;
cp2y = y;
Demo fiddle is here

Applying rounded corners to paths/polygons

I'm gathering some info for a project that has to start within a few weeks.
This project contains a browser-based drawing tool where users can add predefined shapes or forming shapes themselves. Shapes must be selectable, freely scalable and rotatable with a Illustrator-like transformtool (handles).
Predefined shapes that we have in mind are: rectangles, ellipses, half ellipses and (isosceles) triangles.
So far so good, to achieve this I was thinking of RaphaelJS or FabricJS but... Every shape (polygon/path) must be drawn with a certain cornerradius. And the cornerradius must be maintained while scaling, so no distortion occurs. The user can specify the rounding by input.
There's a few obstacles/questions:
Is there some uniform mathemetical formula to apply a cornerradius to the shapes I mentioned? Or must every shape be treated as a mini-project itself? I want to return it as a path or poly, so it can be drawn with SVG or canvas.
Every scale or rotate operation, by dragging the transformhandles, will result in (massive) calculations to retrieve an updated shape I think. Rectangles are the easiest to achieve and except ellipses, all the other shapes will be a lot harder to compute. Is there some way to speed up the process?
I found a site where users can draw flowcharts and apply a cornerradius on almost all shapes the are offered. It works so smoothly, I can't nail how they did it.
Link: https://www.lucidchart.com/ (try button)
For now, I'm a bit clueless, I guess to mediocre in mathematics. Perhaps someone can push me in the right direction and share some experiences?
Thanks in advance.
BTW. Performance is key in this project. The ouput of the drawing must be SVG format.
I ended up having a similar problem, and wasn't able to find a simple solution. I ended up writing a fairly generic corner-rounding function based on Adobe Illustrator's operation. It uses Bezier curves instead of arcs, but I think the result is pretty decent.
It supports rounding with a radius given in the coordinate space of the SVG image or as a fraction of the distance between a corner and its neighbors.
To use this, include rounding.js in your project and call the function:
roundPathCorners(pathString, radius, useFractionalRadius)
The code and some test paths are here: http://embed.plnkr.co/kGnGGyoOCKil02k04snu/preview
This is how the examples from the Plnkr render:
The starting point could be using-svg-curves-to-imitate-rounded-corners. The principle is to convert every corner with shorthand relative cubic (s). This example is very basic and works only with two possible corner cases.
I think expanding this like corner replace with shorthand relative cubic is possible to expand to cover also other path segments. Every segment has a on-curve coordinate point, which have to be replaced with s segment. The math can be interesting part of this solution.
Despite this question being around for some time, some may stop by and try this solution:
var BORDER_RADIUS = 20;
function roundedPath( /* x1, y1, x2, y2, ..., xN, yN */ ){
context.beginPath();
if (!arguments.length) return;
//compute the middle of the first line as start-stop-point:
var deltaY = (arguments[3] - arguments[1]);
var deltaX = (arguments[2] - arguments[0]);
var xPerY = deltaY / deltaX;
var startX = arguments[0] + deltaX / 2;
var startY = arguments[1] + xPerY * deltaX / 2;
//walk around using arcTo:
context.moveTo(startX, startY);
var x1, y1, x2, y2;
x2 = arguments[2];
y2 = arguments[3];
for (var i = 4; i < arguments.length; i += 2) {
x1 = x2;
y1 = y2;
x2 = arguments[i];
y2 = arguments[i + 1];
context.arcTo(x1, y1, x2, y2, BORDER_RADIUS);
}
//finally, close the path:
context.arcTo(x2, y2, arguments[0], arguments[1], BORDER_RADIUS);
context.arcTo(arguments[0], arguments[1], startX, startY, BORDER_RADIUS);
context.closePath();
}
The trick is to start (and stop) at the middle of the first line, and then use the arcTo function which is described very nicely here.
Now you "just" have to find a way to express all of your shapes as polygons.

Plotting points on segment of circumference

This one requires a bit of visualisation, so sorry if my explanation sucks.
So, I have a central point at 0,0. From this point, I am plotting random points on its circumference, at a radius of 350 pixels (random number). For this I am using this code:
var angle = Math.random()*Math.PI*2;
var x = Math.cos(angle)*radius;
var y = Math.sin(angle)*radius;
x+=parent.position.x;
y+=parent.position.y;
The parent.position this is because each point that is plotted also acts as a central node, which has children that act as nodes and so on. This just sets the position of the new node relative the position of its parent.
So this code works perfectly well for the central node. The problem is that once you've branched away from the centre, you want to continue moving in a particular direction to avoid a big cluster of nodes interfering with each other. So, whereas this code plots a point on the circumference, I need to be able to plot a point on a segment of the circumference. I'm thinking maybe about a third of the circumference should be accessible. The other obstacle is that this has to be the CORRECT segment of the circumference i.e If the nodes are branching upwards, I don't want the segment to be the bottom half of the circumference, the branch needs to continue moving in the upwards direction.
I can establish a general direction based on the position of the new parent node relative to the position of its parent. But does anyone have any ideas of how to use this data to reduce the field to the a segment in this direction?
Let me know if that made no sense, it's kinda hard to explain without diagrams.
I think one easy way of doing that would be to split your circle in n segments (each covering 2*PI / n angle). You could set n to whatever you want, depending on how precise you want to be. Then when you calculate a new point x, first get the segment in which x.parent is (relative to its own parent), and use that to put x in the same section wrt x.parent. You could then have something like this:
var getSection = function(point) {
var parent = point.parent;
var angle = Math.acos((point.x - parent.x) / radius) % (Math.PI*2);
var section = Math.floo(angle / (Math.PI * 2 / n))
return section;
}
var section = getSection(parent); // return the index of the section
var angle = (Math.random() + section) * Math.PI * 2 / n
var x = Math.cos(angle)*radius;
var y = Math.sin(angle)*radius;
x+=parent.position.x;
y+=parent.position.y;

Categories