I am trying to use unit/normal vector based gradients in html5 canvas element and transform them afterwards for the desired results. However, I seem to figure troubles which might be because of my lack of math. I am trying to create a simple linear gradient going from 0,0 to 1,0 (i.e. a simple unit gradient going from left to right). Afterwards, I transform the canvas for scaling, rotating and moving the gradient. However, when for example giving a rotation value of 45DEG, the actual gradient gets painted wrong. The right bottom corner has way to much black that is, the gradient seems to be not "big" enough. Here's my code:
var rect = {x: 0, y: 0, w: 500, h: 500};
var rotation = 45 * Math.PI/180;
var sx = 1;
var sy = 1;
var tx = 0;
var ty = 0;
var radial = false;
// Create unit vector 0,0 1,1
var grd = radial ? ctx.createRadialGradient(0, 0, 0, 0, 0, 0.5) : ctx.createLinearGradient(0, 0, 1, 0);
grd.addColorStop(0, 'black');
grd.addColorStop(0.1, 'lime');
grd.addColorStop(0.9, 'yellow');
grd.addColorStop(1, 'black');
// Add our rectangle path before transforming
ctx.beginPath();
ctx.moveTo(rect.x, rect.y);
ctx.lineTo(rect.x + rect.w, rect.y);
ctx.lineTo(rect.x + rect.w, rect.y + rect.h);
ctx.lineTo(rect.x, rect.y + rect.h);
ctx.closePath();
// Rotate and scale unit gradient
ctx.rotate(rotation);
ctx.scale(sx * rect.w, sy * rect.h);
ctx.fillStyle = grd;
// Fill gradient
ctx.fill();
And here's the fiddle to try it out:
http://jsfiddle.net/4GsCE/1/
Curious enough, changing the unit linear gradient vector to a factor of about 1.41 makes the gradient look right:
ctx.createLinearGradient(0, 0, 1.41, 0)
Which can be seen in this fiddle:
http://jsfiddle.net/4GsCE/2/
But I couldn't figure how to calculate that factor?
Since you want to use normalized gradients, you have to decide how to normalize. Here you choose to center the gradient, and to have its (x,y) in the [-0.5, 0.5 ] range.
First issue is that the linear gradient is not centered, it's in the [0, 1.0] range.
Normalize them the same way :
var linGrd = ctx.createLinearGradient(-0.5, 0, 0.5, 0);
Second issue is that you must translate to the center of your figure, then scale, then draw in a normalized way.
Meaning you must use same coordinate system as your gradients.
Since you were both drawing a shape having (w,h) as size AND using a scale of (w,h), you were drawing a ( ww, hh ) sized rect.
Correct draw code is this one :
function drawRect(rect, fill) {
ctx.save();
// translate to the center of the rect (the new (0,0) )
ctx.translate(rect.x + rect.w / 2, rect.y + rect.h / 2);
// Rotate
ctx.rotate(rotation);
// scale to the size of the rect
ctx.scale(rect.w, rect.h);
// ...
ctx.fillStyle = fill;
// draw 'normalized' rect
ctx.fillRect(-0.5, -0.5, 1, 1);
ctx.restore();
}
Notice that by default the radialGradient will end at a distance of 0.5, meaning, if you are filling a rect, that it will fill the corners with the last color of the gradient. Maybe you want the corners to end the gradient.
In that case, you want to have the gradient to reach its value at a distance of :
sqrt ( 0.5*0.5 + 0.5*0.5 ) = 0.7 ( pythagore in the normalized circle)
So you'll define your normalized gradient like :
var fullRadGrd = ctx.createRadialGradient(0, 0, 0, 0, 0, 0.7) ;
http://jsfiddle.net/gamealchemist/4GsCE/4/
Related
I'm following a tutorial by George Francis in the tutorial after some initial examples he shows how to use image data to create random layouts.
I'm trying to work out how to get the image data from a canvas created using paper.js, as I need to get the rgb values from each individual pixel on the canvas
Link to codepen
Unknowns:
Do I need to use the rasterize() method on the shape I've created?
Currently I am attempting the following:
// create a white rectangle the size of the view (not sure I need this but doing it so that there are both white and black pixels)
const bg = new paper.Path.Rectangle({
position: [0,0],
size: view.viewSize.multiply(2),
fillColor: 'white'
})
// create a black rectangle smaller than the view size
const shape = new paper.Path.RegularPolygon({
radius: view.viewSize.width * 0.4,
fillColor: 'black',
strokeColor: 'black',
sides: 4,
position: view.center
})
// So far so good shapes render as expected. Next put the shapes in a group
const group = new paper.Group([bg,shape])
// rasterise the group (thinking it needs to be rasterized to get the pixel data, but again , not sure?)
group.rasterize()
// iterate over each pixel on the canvas and get the image data
for(let x = 0; x < width; x++){
for(let y = 0; y < height; y++){
const { data } = view.context.getImageData(x,y,1,1)
console.log(data)
}
}
Expecting: To get an array of buffers where if the pixel is white it would give me
Uint8ClampedArray(4) [0, 0, 0, 0, buffer: ArrayBuffer(4),
byteLength: 4, byteOffset: 0, length: 4]
0: 255
1: 255
2: 255
//(not sure if the fourth index represents (rgb'a')?
3: 255
buffer:
ArrayBuffer(4)
byteLength: 4
byteOffset: 0
length: 4
Symbol(Symbol.toStringTag): (...)
[[Prototype]]: TypedArray
and if the pixel is black I should get
Uint8ClampedArray(4) [0, 0, 0, 0, buffer: ArrayBuffer(4),
byteLength: 4, byteOffset: 0, length: 4]
0: 0
1: 0
2: 0
3: 0
buffer:
ArrayBuffer(4)
byteLength: 4
byteOffset: 0
length: 4
Symbol(Symbol.toStringTag): (...)
[[Prototype]]: TypedArray
i.e either 255,255,255 (white) or 0,0,0(black)
Instead, all the values are 0,0,0?
I think that your issue was that at the time where you are getting the image data, your scene is not yet drawn to the canvas.
In order to make sure it's drawn, you just need to call view.update().
Here's a simple sketch demonstrating how it could be used.
Note that you don't need to rasterize your scene if you are using the Canvas API directly to manipulate the image data. But you could also rasterize it and take advantage of Paper.js helper methods like raster.getPixel().
// Draw a white background (you effectively need it otherwise your default
// pixels will be black).
new Path.Rectangle({
rectangle: view.bounds,
fillColor: 'white'
});
// Draw a black rectangle covering most of the canvas.
new Path.Rectangle({
rectangle: view.bounds.scale(0.9),
fillColor: 'black'
});
// Make sure that the scene is drawn into the canvas.
view.update();
// Get the canvas image data.
const { width, height } = view.element;
const imageData = view.context.getImageData(0, 0, width, height);
// Loop over each pixel and store all the different colors to check that this works.
const colors = new Set();
const length = imageData.data.length;
for (let i = 0; i < length; i += 4) {
const [r, g, b, a] = imageData.data.slice(i, i + 4);
const color = JSON.stringify({ r, g, b, a });
colors.add(color);
}
console.log('colors', [...colors]);
I'm trying to move an arrow, which could be rotated, in a straight line. I'm having some difficulty coming up with the correct formula to use. I know it should probably involve sine and cosine, but I've tried various configurations and haven't been able to get something that works.
Here's a picture of my scene with the arrow and bow
rotateNumber is an integer like -1 (for 1 left rotation), 0 (no rotation), 1 (1 right rotation), etc.
rotateAngle is 10 degrees by default.
Here's the code to move the arrow:
if (arrowMoving) {
var rAngle = rotateAngle * rotateNumber;
var angleInRad = rAngle * (Math.PI/180);
var stepSize = 1/20;
arrowX += stepSize * Math.cos(angleInRad);
arrowY += stepSize * Math.sin(angleInRad);
DrawArrowTranslate(arrowX, arrowY);
requestAnimFrame(render);
} else {
DrawArrow();
arrowX = 0;
arrowY = 0;
}
Here's the code to draw and translate the arrow:
function DrawArrowTranslate(tx, ty) {
modelViewStack.push(modelViewMatrix);
/*
var s = scale4(0.3, -0.7, 1);
var t = translate(0, -4, 0);
*/
var s = scale4(0.3, -0.7, 1);
var t = translate(0, -5, 0);
var t2 = translate(0 + tx, 1 + ty, 0)
// rotate takes angle in degrees
var rAngle = rotateAngle;
var r = rotate(rAngle, 0, 0, 1);
var m = mult(t, r);
var m = mult(m, t2);
modelViewMatrix = mat4();
modelViewMatrix = mult(modelViewMatrix, m);
modelViewMatrix = mult(modelViewMatrix, s);
/*
// update bounding box
arrowBoundingBox.translate(0, -5);
arrowBoundingBox.rotate(rAngle);
arrowBoundingBox.translate(0, 1);
arrowBoundingBox.scale(0.3, -0.7);
*/
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));
gl.drawArrays( gl.LINE_STRIP, 1833, 4);
gl.drawArrays( gl.LINE_STRIP, 1837, 4);
modelViewMatrix = modelViewStack.pop();
}
Your code looks quite correct, but you should eliminate the use of the rotateNumber. You can just use positive and negative angles for rotation instead, eliminating what I imagine is the cause of error here.
Sin and Cos can certainly handle angles of any magnitude positive, negative, or zero.
Good luck!
I figured out the problem. I was translating after rotating when I needed to translate before rotating as the rotation was messing up the translation.
I have two points with their coordinates and have to draw a circle. One point is the center and the other one is on the edge of the circle, so basically the distance between the two points is the radius of the circle. I have to do this in MFC. I tried this, but the circle is not drawn properly. Usually it's bigger than it should be.
double radius = sqrt((c->p1.x*1.0 - c->p1.y) * (c->p1.x - c->p1.y) +
(c->p2.x - c->p2.y) * (c->p2.x - c->p2.y));
CPen black(PS_SOLID,3,RGB(255,0,0));
pDC->SelectObject(&black);
pDC->Ellipse(c->p1.x-radius , c->p1.y-radius, c->p1.x+radius, c->p1.y+radius);
p1 and p2 are points. The circle is drawn as an incircle in a rectangle. The arguments in Ellipse() are the top left and bottom right corners of a rectangle.
your radius computations is wrong ... it should be:
double radius = sqrt(((c->p2.x - c->p1.x)*(c->p2.x - c->p1.x))
+((c->p2.y - c->p1.y)*(c->p2.y - c->p1.y)));
Here is an implementation to calculate the radius, that's easier to read (and correct):
#include <cmath>
int findRadius( const CPoint& p1, const CPoint& p2 ) {
// Calculate distance
CPoint dist{ p2 };
dist -= p1;
// Calculate radius
double r = std::sqrt( ( dist.x * dist.x ) + ( dist.y * dist.y ) );
// Convert to integer with appropriate rounding
return static_cast<int>( r + 0.5 );
}
You can use this from your rendering code:
int radius = findRadius( c->p1, c->p2 );
CPen black( PS_SOLID, 3, RGB( 255, 0, 0 ) );
// Save previously selected pen
CPen* pOld = pDC->SelectObject( &black );
pDC->Ellipse( c->p1.x - radius, c->p1.y - radius,
c->p1.x + radius, c->p1.y + radius );
// Restore DC by selecting the old pen
pDC->SelectObject( pOld );
I am using three.js to create a simple 3d vector environment. I am using lines to represent all 3 vector compontens x, y, z and a line for the final vector representation. Problem is that setting the width of a line is not working in Windows. The workaround that I try to implement is placing a cylinder onto the line (see red object in image below).
That is my current result:
As you see I am not able to rotate the cylinder to the correct position.
I faced the problem that the rotation center of the cylinder is in the middle of the object, so I moved the rotation point to the beginning of the cylinder. But still, rotation is not working correctly. I guess, the rotations around the axis influence each other.
Here is the code:
// VEKTOR
var vektor = {};
vektor._x = 2;
vektor._y = 1.5;
vektor._z = 1;
vektor._length = Math.sqrt(vektor._x*vektor._x + vektor._y*vektor._y + vektor._z*vektor._z);
// CYLINDER
var cyl_material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
// cylinder which is our line that represents the vector
var cyl_width = 0.025; // default line width
var cyl_height = vektor._length;
// THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
var cylGeometry = new THREE.CylinderGeometry(cyl_width, cyl_width, cyl_height, 20, 1, false);
// translate the cylinder geometry so that the desired point within the geometry is now at the origin
// https://stackoverflow.com/questions/12746011/three-js-how-do-i-rotate-a-cylinder-around-a-specific-point
cylGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, cyl_height/2, 0 ) );
var cylinder = new THREE.Mesh(cylGeometry, cyl_material);
updateCylinder();
scene.add( cylinder );
And the function updateCylinder trys to do the rotation.
function updateCylinder() {
// ... stuff, then:
cylinder.rotation.x = Math.atan2(vektor._z,vektor._y);
cylinder.rotation.y = 0.5*Math.PI+Math.atan2(vektor._x,vektor._z);
cylinder.rotation.z = Math.atan2(vektor._x,vektor._y);
}
Here is the current demo: http://www.matheretter.de/3d/vektoren/komponenten/
What am i doing wrong with the rotation? How to implement it so that the cylinder is following the vector line?
Thanks for your help.
If you want to transform a cylinder so that one end is at the origin and the other end points toward a specific point, here is the pattern you can follow:
First, transform your geometry so one end of the cylinder is at the origin, and the other end (the top) is on the positive z-axis.
var geometry = new THREE.CylinderGeometry( 0, 1, length, 8, 1, true );
geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, length / 2, 0 ) );
geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
Then create your mesh, and call the lookAt() method:
var mesh = new THREE.Mesh( geometry, material );
mesh.lookAt( point );
three.js r.67
In this answer to my recent question, there is some code that draws a graph, but I can't manage to edit it into something that accepts any list of points as a parameter.
I'd like the Drawing method to accept these parameters:
List of Vector2, Point or VertexPositionColor, I can work with whichever.
Offset for the whole graph
These optional requirements would be appreciated:
Color that may override VertexPositionColor's color and apply to all points.
Size of the graph, so it can be shrunk or expanded, either as Vector2 as multiplier, or Point as target size. Maybe even combine this with offset in Rectangle.
And if it's possible, I'd like to have it all in a class, so graphs can be used separately from each other, each with its own Effect.world matrix, etc.
Here is that code (by Niko Drašković):
Matrix worldMatrix;
Matrix viewMatrix;
Matrix projectionMatrix;
BasicEffect basicEffect;
VertexPositionColor[] pointList;
short[] lineListIndices;
protected override void Initialize()
{
int n = 300;
//GeneratePoints generates a random graph, implementation irrelevant
pointList = new VertexPositionColor[n];
for (int i = 0; i < n; i++)
pointList[i] = new VertexPositionColor() { Position = new Vector3(i, (float)(Math.Sin((i / 15.0)) * height / 2.0 + height / 2.0 + minY), 0), Color = Color.Blue };
//links the points into a list
lineListIndices = new short[(n * 2) - 2];
for (int i = 0; i < n - 1; i++)
{
lineListIndices[i * 2] = (short)(i);
lineListIndices[(i * 2) + 1] = (short)(i + 1);
}
worldMatrix = Matrix.Identity;
viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
projectionMatrix = Matrix.CreateOrthographicOffCenter(0, (float)GraphicsDevice.Viewport.Width, (float)GraphicsDevice.Viewport.Height, 0, 1.0f, 1000.0f);
basicEffect = new BasicEffect(graphics.GraphicsDevice);
basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
basicEffect.VertexColorEnabled = true; //important for color
base.Initialize();
}
And the drawing method:
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
PrimitiveType.LineList,
pointList,
0,
pointList.Length,
lineListIndices,
0,
pointList.Length - 1
);
}
The Graph class that does the requested can be found here.About 200 lines of code seemed too much to paste here.
The Graph is drawn by passing a list of floats (optionally with colors) to its Draw(..) method.
Graph properties are:
Vector2 Position - the bottom left corner of the graph
Point Size - the width (.X) and height (.Y) of the graph. Horizontally, values will be distributed to exactly fit the width. Vertically, all values will be scaled with Size.Y / MaxValue.
float MaxValue - the value which will be at the top of the graph. All off the chart values (greater than MaxValue) will be set to this value.
GraphType Type - with possible values GraphType.Line and GraphType.Fill, determines if the graph will be drawn line only, or bottom filled.
The graph is drawn with a line list / triangle strip.