AS3 - geometry - perspective projection of a point on a 2D plane - actionscript-3

I'm currently struggling on a problem that seems far beyond my maths capacities (been a long time since I've made some proper maths...) and I would appreciate some help on that.
Here's my setting :
I got some simple shapes (rectangles), and I "project" their bottom points on a line, coming from an Origin point.
Up to this point everything is fine.
But now I'd like to draw the original shape distorted as if it was projected with some perspective on a plane.
Please consider that I have nothing related to any rotation, isometric or any 3D or fake 2D perspective in my code, I'm only trying to draw some shapes using the graphics library to only have a feeling of something real.
Here's a quick drawing of what I'm trying to do :
What I know :
Origin point coordinates
the rect position & sizes
the red line position
the A & B points coordinates
What I want to determine is the coordinates of the C & D points, thing that could be easy if I wasn't struggling to find the "Origin bis" coordinates.
What I'm trying to do is to fake the projection of my rectangle on something that can be considered as a "floor" (related to the plane where my original rectangle is that can be seen as a wall).
Maybe I'm over-complicating the problem or maybe I fail to see any other easier way to do it, but I'm really not good anymore in any geometry or maths thing... :-(
Thanks a lot for your answers !

hmm i don't know if I undestood it correctly but I think you have too few input parameters:
you said the following information is given:
Origin point coordinates
the rect position & sizes
the red line position
the A & B points coordinates
I don't think it is possible to get your projected rectangle with this information alone.
Additionally, I think your green lines and the 'origin Bis' aren't helpful as well.
Perhaps, try this:
Supose, a blue line going through the points C & D is given as well.
Then you could find your projected rectangle by projecting the top of the rectangle onto that blue line.
So in summary:
You define an origin + two parallel lines, a red and a blue one.
Then you can project the top of the rect onto the blue line and the bottom of the rect onto the red line, yielding the points A,B,C,D
I hope this helps.

If I'm right, this code will show what you wanted to see.
First of all, I've ignored your initial setup of objects and information, and focused on the example situation itself; fake-projecting shadow for a "monolith" (any object is possible with the example below, even textured)
My reason was that it's really quite easy with the Matrix class of ActionScript, a handy tool worth learning.
Solution:
You can use the built-in Matrix class to do skew transform on DisplayObjects.
Try this example:
(The "useful" part lies in the _EF EnterFrame handler ;) )
import flash.display.MovieClip;
import flash.geom.Matrix;
import flash.events.Event;
import flash.display.BitmapData;
const PIP180:Number = Math.PI / 180;
const MAX_SHADOW_HEIGHT_MULTIPLIER:Number = 0.25; // you can also calculate this from an angle, like ... = Math.sin(angle * PIP180);
const ANIM_DEG_PER_FRAME:Number = 1.0 * PIP180; // the shadow creeps at a +1 degree per frame rate
var tx:BitmapData = new MonolithTexture(); // define this BitmapData in the library
var skew:Number = -10 * PIP180; // initial
var mono:MovieClip = new MovieClip();
mono.graphics.beginBitmapFill(tx);
// drawn that way the registration point is 0,0, so it's standing on the ground
mono.graphics.drawRect(0, -tx.height, tx.width, tx.height);
mono.graphics.endFill();
// align monolith to the "ground"
mono.x = stage.stageWidth / 2;
mono.y = stage.stageHeight - 100;
// make it be 100x300 pixel
mono.width = 100;
mono.height = 300;
var shad:MovieClip = new MovieClip();
// colored:
shad.graphics.beginFill(0x000000);
// or textured:
//shad.graphics.beginBitmapFill(tx);
shad.graphics.drawRect(0, -tx.height, tx.width, tx.height);
shad.graphics.endFill();
addChild(shad); // shadow first
addChild(mono); // then the caster object
addEventListener(Event.ENTER_FRAME, _EF);
function _EF(e:Event):void {
// animate skew on the positive half circle
skew = (skew + ANIM_DEG_PER_FRAME) % Math.PI;
// Matrix takes 6 parameters: a, b, c, d, x, y
// for this shadow trick, use them as follows:
// a = width scaling (as mono and shad are drawn in the same way, copy mono.scaleX for a perfect fit
// b = 0, because we don't want to project the vertical axis of transformation to the horizontal
// c = horizontal skew
// d = height scaling * skew * making it a bit flat using the constant
// x = mono.x, ...
// y = mono.y since originally mono and shad look alike, only the Matrix makes shad render differently
var mtx:Matrix = new Matrix(mono.scaleX, 0, Math.cos(skew), mono.scaleY * Math.sin(skew) * MAX_SHADOW_HEIGHT_MULTIPLIER, mono.x, mono.y);
shad.transform.matrix = mtx;
}
Now all you got to know to utilize this in your case, is the following N factors:
Q1: from what angle you want to project the shadow?
A1: horizontal factor is the skew variable itself, while vertical angle is stored as constant here, called MAX_SHADOW_HEIGHT_MULTIPLIER
Q2: do you want to project shadow only "upwards", or freely?
A2: if "upwards" is fine, keep skew in the positive range, otherwise let it take negative values as well for a "downward" shadow
P.S.: if you render the internals of the objects that they don't snap to 0 y as a base point, you can make them seem float/sink, or offset both objects vertically with a predefined value, with the opposite sign.

You face 1 very simple problem, as you said:
'What I want to determine is the coordinates of the C & D points, thing that could be easy if I wasn't struggling to find the "Origin bis" coordinates.'
But these co-ordinates relate to each other, so without one (or another value such as an angle) you cannot have the other. If you are to try this in 3D you are simply allowing the 3D engine to define 'Origin bis' and do your calculating for C and D itself.
So regardless you will need an 'Original bis', another value relating to the redline or your Rect for which to calculate the placement of C and D.
I remember making stuff like this and sometimes it's better to just stick with simple, you either make an 'Original bis' defines by yourself (it can be either stationary or move with the player/background) and get C and D the way you got A and B only that you use a lower line than the red line, or as I would of done, once you have A and B, simple skew/rotate your projection from those points down a bit further, and you get something the same as an 'Original bis' that follows the player. This works fine at simulating 'feeling of something real' but sadly as has been said, it looking real depends on what you are portraying. We do not know what the areas above or below the red line are (sky/ground, ground/water) and whether 'Origin' and 'Origin bis' is your light source, vanishing point, etc.

Related

Line quality is very low with jagged or blurry edges

I'm working on a drawing app and the line quality seems to be very low and jagged compared to other drawing apps.
Or it might be that other apps are doing something different than I'm doing.
What I have done so far is use the graphics property to draw the lines. I also collect the mouse positions on the mouse move events for assigning to a path later on. Summarized it:
MouseDownHandler:
mouseDownPoint.x = event.stageX;
mouseDownPoint.y = event.stageY;
drawCommands.push(GraphicsPathCommand.MOVE_TO);
simplePath = "M " + mouseDownPoint.x + " " + mouseDownPoint.y;
MouseMoveHandler:
line.graphics.lineStyle(lineWeight, lineColor, lineAlpha, pixelHinting);
line.graphics.moveTo(previousPoint.x, previousPoint.y);
scaledPoint = new Point(localPoint.x/scaleX, localPoint.y/scaleY);
line.graphics.lineTo(scaledPoint.x, scaledPoint.y);
previousPoint.x = scaledPoint.x;
previousPoint.y = scaledPoint.y;
simplePath += " L " + scaledPoint.x + " " + scaledPoint.y;
MouseUpHandler:
myPath.data = simplePath;
As I draw I update the line (which is a UIComponent but could just as well be a Shape or Sprite - anything with a graphics property). At the same time I keep track of the mouse locations in the simplePath string.
When the mouse is up I clear the line graphics and show a path graphic element. The Path isn't important to this but I noticed it looks slightly cleaner than the line that had been drawn. That might be because it has pixel hinting (it's not much cleaner). Sometimes there are artifacts. And I'm including it in case I need to use a path for some reason.
Here is the screen shot:
The pixel hinted version looks crisper but it still is far below the quality of the line drawing in other apps and in some cases it makes it look more jagged. Is there something I'm missing?
Note: I included graphics2d and canvas2d because I believe this may not be related to the specific language or platform but might be related to drawing graphics in general.
The green line is produced by Graphics.cubicCurveTo(...) method. Initially you have a list of user-provided points A1,A2,A3...An. In order to use cubic curves you also need to figure 2 control points CFk (forward) and CBk (backward), for each Ak respectively, so you draw that big curve starting from A1 and every curve piece from Ak-1 to Ak will take arguments .cubicCurveTo(CFk-1, CBk, Ak);
For each Ak (except for A1 and An) you can calculate CFk and CBk as following:
(vector)AForward = (vector)(Ak+1 - Ak-1)
(vector)AForward.length = (vector)(Ak+1 - Ak).length / 3
CFk = Ak + (point)AForward
(vector)ABackward = (vector)(Ak-1 - Ak+1)
(vector)ABackward.length = (vector)(Ak-1 - Ak).length / 3
CBk = Ak + (point)ABackward
Then, there are A1 and An that are left out, but I am sure you can figure them on your own.
For vector math you can use ru.delimiter.math.Vector2D class (works with both Cartesian and Polar coordinates) from my small collection of useful things: https://bitbucket.org/thydmitry/ru.delimiter/src/9083fb46ce1c/classes/ru/delimiter/math/
P.S. Maybe you don't need to go that extreme and will be fine with the red line, that is a simple .curveTo(Ak, (Ak + Ak+1)/2);
UPD: a simple algorithm to inscribe a curve into zigzag provided by an array of points.
function middle(A:Point, B:Point):Point
{
return new Point((A.x + B.x) / 2, (A.y + B.y) / 2);
}
function drawTo(target:Point):void
{
graphics.lineTo(target.x, target.y);
}
function bendTo(control:Point, target:Point):void
{
graphics.curveTo(control.x, control.y, target.x, target.y);
}
// This should contain at least 2 points before you start drawing.
var PP:Vector.<Point>;
// Go to the start position.
graphics.lineStyle(0, 0xFF0000);
graphics.moveTo(PP[0].x, PP[0].y);
// Draw a straight line to the center of the first zigzag segment.
drawTo(middle(PP[0], PP[1]));
// For each 3 consequent points A,B and C, connect
// the middle of AB and the middle of BC with a curve.
for (var i:int = 2; i < PP.length; i++)
{
bendTo(PP[i - 1], middle(PP[i - 1], PP[i]));
}
// Connect the center of the last zigzag segment with the end point.
drawTo(PP[PP.length - 1]);
There are multiple reasons:
Stage quality. In Flash Player you can set the stage quality to LOW, MEDIUM, HIGH, BEST, 8x8, 8x8Linear, 16x16 and 16x16Linear. This affects if there is antialiasing applied on lines / paths and how many times it's applied. Increasing the quality helps but in 8x8 and higher quality there are bugs in the Flash Player (font size reduced by 25% on non-embedded fonts, graphics artifacts, gradient fills color count reduced).
Pixel snapping. If you have a 1px line that ends up positioned on a half pixel it is anti aliased over two lines. Normally antialiasing increases quality but in the case of an offset single pixel line it reduces quality. Setting pixel snapping helps.
Using curveTo and cubicCurveTo instead of line points as #Organis suggested. Not sure how to do this yet.
Will try to post images of the difference in each case when I get a chance.

Extract derived 3D scaling from a Sprite to set to a 2D billboard

I am trying to get the derived position and scaling of a 3D Sprite and set them to a 2D Sprite.
I have managed to do the first part like this:
var p:Point = sprite3d.local3DToGlobal(new Vector3D(0,0,0));
billboard.x = p.x;
billboard.y = p.y;
But I can't get the scaling part correctly. I am trying this:
var mat:Matrix3D = sprite3d.transform.getRelativeMatrix3D(stage); // get derived matrix(?)
var scaleV:Vector3D = mat.decompose()[2]; // get scaling vector from derived matrix
var scale:Number = scaleV.length;
billboard.scaleX = scale;
billboard.scaleY = scale;
...but the result is apparently wrong.
PS. One might ask what I am trying to achieve. I am trying to create "billboard" 3D sprites, i.e. sprites which are affected by all 3D transformations except rotations, thus they always face the "camera".
The documentation says that you get the vector correctly, but its coefficients don't seem to be added together to form a single length value. Try first an unscaled sprite, and check if you're receiving a sqrt(3) value as its length. If yes, then you should use 0th element of the vector as X scale, and 1th as Y scale. I'm not sure what to do with 2nd element (in this case it'll be a Z scale, either divide both scales by it, or multiply by it). Hope that helped.

HTML5 Canvas - Wireframe Sphere in 2d

I'm looking to draw a 3D wire frame sphere in 2D Canvas. I'm not a math ninja by any means, so I'm wondering if anyone knows a simple way to draw one in Canvas using lineto arc connections and drawing it with :math:
I would appreciate any assistance.
Something like this: http://en.wikipedia.org/wiki/File:Sphere_wireframe_10deg_6r.svg
I'm hoping this is a simple equation, but if you know that it isn't (i.e. drawing that would be a lot of code), I would appreciate knowing that as well as I may need to reconsider what I wanna do.
The easiest for you would probably to view the source of the SVG file (here) and recreate those paths using canvas commands.
If you want an actual 3d sphere, projected onto 2d space, I'd suggest using a library like Three.js
You can also look at some of the math I've done here: swarms
The _3d and Matrix modules should be all that you need.
This time SO didn't help me, so I've helped myself and here it is: a pure HTML5 + JavaScript configurable rendering or a wireframe sphere.
I started from this excellent post and then went on. Basically I collected some vertex generation code from Qt3D and adapted to JS.
I'm not 100% sure the rotation functions are correct, but you are welcome to contribute back in case you find errors.
To be clearer, I've distinguished Z positions and draw white on the front and gray on the back.
Here's the result (16 rings x 32 slices) and related jsFiddle link
Enjoy
This is an old thread, but I had the same question and could not find any existing satisfying answer. That is, an answer other than "use WebGL" or "use Three.js". Lo and behold, I am the bearer of great news: it is actually possible to render such a sphere using exclusively Canvas2D's ellipse function, giving us:
a straightforward implementation (~130 lines everything included)
no need for computing vertices and edges
sexy smooth edges
You can find a demo on JSBin, for posterity, with a bunch of options.
The key is to notice that the "wireframe" we're trying to draw is solely composed of circles, and every circle rotated in 3d space will get projected to the camera as an ellipse. The question, then, is: how to find the ellipse corresponding to the projection of the rotated circle?
As we are only interested in circles that lay on the surface of the sphere, we can characterize each of them by the (inter)section of a plane and the (unit) sphere. Therefore, each circle can be described by a normal vector and an offset -1 < o < 1.
Then it's not too difficult to compute and draw the ellipse resulting from the projection of the circle:
function draw_section(n, o = 0) {
let {x, y, z} = project(_p, n) // project normal on camera
let a = atan2(y, x) // angle of projected normal -> angle of ellipse
let ry = sqrt(1 - o * o) // radius of section -> y-radius of ellipse
let rx = ry * abs(z) // x-radius of ellipse
let W = sqrt(x * x + y * y)
let sa = acos(clamp(-1, 1, o * (1 / W - W) / rx || 0)) // ellipse start angle
let sb = z > 0 ? 2 * PI - sa : - sa // ellipse end angle
ctx.beginPath()
ctx.ellipse(x * o * RADIUS, y * o * RADIUS, rx * RADIUS, ry * RADIUS, a, sa, sb, z <= 0)
ctx.stroke()
}
The disks from your example image can be obtained by:
rotating a plane around the z axis
shifting a plane along the z axis
function draw_arcs() {
for (let i = 10; i--;) {
let a = i / 10 * Math.PI
draw_section(vec.set(_n, cos(a), sin(a), 0))
}
for (let i = 9; i--;) {
let a = (i + 1) / 10 * Math.PI
draw_section(Z, cos(a))
}
}
A nice benefit of this method is that you can do this "shifting a plane along the Z axis" for all axes, resulting in a lovely wireframe that would be hard to reproduce if computing vertices and edges by hand:
The only change was the following:
function draw_arcs() {
for (let i = 9; i--;) {
let a = (i + 1) / 10 * Math.PI
draw_section(Z, cos(a))
draw_section(X, cos(a))
draw_section(Y, cos(a))
}
}
The function draw_section above was carefully crafted so that it only draws the camera-facing arc of a given section, which means we get occlusion-culling for free.
(and my dirty trick to render the back of the sphere with a different color is to run draw_arcs again after flipping the canvas)
It's also possible to use 2 radial gradients to have some fake depth shading like in your image:
Sadly browsers seem to struggle a lot when drawing paths with gradients.
There are but two drawbacks I see right now:
performance may vary between browsers. It's likely some optimization could be done, like merging successive calls to .stroke() into one. Frankly quite surprised by how slow using ellipse seems to be at times.
it's a parallel projection rather than a perspective one. If we were to add perspective, projected circles would still appear as ellipses, but the calculation of the ellipse would be a tad more involved. I haven't done it yet, I expect it to be possible, might update my answer if I succeed.
Look at this one: http://jsfiddle.net/aJMBp/
you should just draw a lot of these lines to create a complete sphere. This is a good starting point, give me 5 minutes and I'll see if I can improve it to draw a sphere.
Getting better:
http://jsfiddle.net/aJMBp/1/
Ok, thats def out of my capacity. However, another little improvement here: http://jsfiddle.net/aJMBp/2/

How to create a circular "brush tip" in HTML5 canvas drawing application?

I'm using HTML5 lineTo but any stroke greater than 1 creates squarish looking corners on the lines (the stroke extends perpendicular to the path of the line you draw). I want to create a circular brush tip, similar to http://muro.deviantart.com.
Any ideas?
The corners can be rounded by setting the line cap.
ctx.lineCap = "round"
You can also apply a bezier curve to the overall line to create a smoother overall line, by, for each point in the line P'0, …, P'n + 1, applying the equation P'k = (k/(n+1))Pk-1+(1-(k/(n+1)))Pk [NB: You might do well to select which points to which you apply the smoothing of the bezier curve by setting a threshold, perhaps on the angle between Pn and Pn+1]
Combining these two techniques with a standard box blur to the line itself will give you a much smoother appearing line.
Edit
From what I can tell, there's actually a number of ways to do this – which you use is entirely up to you. I'll give you an example, and let you decide: Assume you have a path drawn from a beginning point pm (mousedown) to an endpoint (mouseup) pn. That path is made up of subpaths (the points joined by miters). We can draw the path to the context from p0 to p1 with lineTo() and stroke() as normal. Just from watching console output, the points at which the subpaths join is the mousemove event firing. Record these points in order in an array.
Of course if we draw this to the main context, we have a problem removing it, so this should be done to a buffer context (an additional canvas element, for instance). The buffer is cleared, and we use the points of the miters to calculate the curve. bezierCurveTo prints a cubic function (B(t) = (1-t)3P0+3(1-t)2P1+3(1-t)t2P2+t3P3, t ∈ [0,1]. Step through your array (think for loop) recalculating the line with those points, updating the curve from P0 to Pn-3. (Doing quick head-math. You might need to think over this endpoint. All of this is dependent upon which arcing equation you use).
So let me see if I can do something with this... I'm not testing it so I guarantee bugginess.
// Assume:
// bfr = buffer context.
// ctx = main context.
// md = boolean value for mousedown
// pts = []; <-- already contains lp (below) at pts[0];
// We've also recorded Pm in associative array lp [last point]
// Draw is fired on mousemove. Mousemove records a current point in associative array cp
draw = function() {
if(md) {
bfr.beginPath();
bfr.moveTo(lp.x-.5, lp.y-.5);
bfr.lineTo(cp.x-.5, cp.y-.5);
pts.push({cp.x, cp.y});
bfr.stroke();
}
}
// Optionally, you could make this function recursive.
// This assumes that you want to estimate the curve based on the whole line.
bezier = function(pts) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
for( var i = 0; i < pts.length - 3; i++ ) {
ctx.bezierCurveTo( pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y, pts[i+3].x, pts[i+3].y);
}
ctx.stroke();
}
Again, this is what I see – someone else may have an entirely different and I'm sure better interpretation. I'm trying to tear chunks of things I've done and put them together with some new code quickly to give you some idea.

Difference between defining sprite's x, y coordinates, and painting object in some location

I have a task:
I need to place about 100 sprites on one canvas (with prepared grid on it). I need to place them as invisible (circles) stones, on the board, and make visible only on mouseover.
The problem I come across is following, I can't place those objects accurately into the nodes on the grid.
E.g.
if I define stones (it's just a sprite, as I said earlier) this way:
var stone:StoneSprite = new StoneSprite();
stone.x = this.x + 2*cellWidth;
stone.graphics.beginFill( 0x000000 );
stone.graphics.drawCircle(stone.x , this.y + cellWidth, cellWidth/3 );
stone.graphics.endFill();
rawChildren.addChild(stone);
They don't sit on the node...
See image:
http://img.skitch.com/20091014-kuhfyjeg1g5qmrbyxbcerp4aya.png
And if I do it this way:
var stone:StoneSprite = new StoneSprite();
stone.graphics.beginFill( 0x000000 );
  stone.graphics.drawCircle(this.x + 2*cellWidth , this.y + cellWidth, cellWidth/3 );
  stone.graphics.endFill();
rawChildren.addChild(stone);
The stone is displayed correctly in the grid node... See image 2:
http://img.skitch.com/20091014-f595tksjxramt98s7yfye591bh.png
So I wonder what is the difference between these 2 approaches.
Also, I think I need to pass correct coordinates to the stone class... In case I would like to change some properties of the stone object. E.g. visibility, or radius.
Could you please suggest, what's wrong in defining coordinates as stone.x, stone.y
How to fix the problem with incorrect positioning.
Would really appreciate ideas about the problem, I am trying to solve for so long :(
Assume x & y are 30 and cellWidth is 30.
First Example:
stone.x = 30 + 60; //90
drawCircle(90, 60, 10);
This means if you were to draw a rectangle around your circle, it would be at [170,50]. (x,y).
Second Example:
stone.x = 0;
drawCircle(90, 60, 10)
This means the rectangle around your circle is at [80,50];
In the first example, you are moving the sprite to position x==90. Then drawing a circle whose center is at x==90 inside the sprite. So relative to this, you're at x==180. But because a circle's x,y coords are the center, subtract 10 for the radius to get the boundary x position.
In the second example, the sprite defaults to position x==0 relative to this and you're drawing the circle inside the sprite at position x==90. (therefore it begins at x==80).
I am not sure what's causing the issue - might be some padding induced by the container - can't say without testing. But I believe that adding a Sprite (say board) to canvas.rawChildren and using it as the parent for the grid and stones would fix the issue.