What value fed to strokeWidth() will give a stroke width of one pixel regardless of the current scale() setting?
I think strokeWeight(0) should work. Here is an example:
void setup() {
size(100,100);
noFill();
scale(10);
// 1st square, stroke will be 10 pixels
translate(3,3);
strokeWeight(1);
beginShape();
vertex(-1.0, -1.0);
vertex(-1.0, 1.0);
vertex( 1.0, 1.0);
vertex( 1.0, -1.0);
endShape(CLOSE);
// 2nd square, stroke will be 1 pixel
translate(3,3);
strokeWeight(0);
beginShape();
vertex(-1.0, -1.0);
vertex(-1.0, 1.0);
vertex( 1.0, 1.0);
vertex( 1.0, -1.0);
endShape(CLOSE);
}
Kevin did offer a couple of good approaches.
Your question doesn't make it clear what level of comfort you have with the language. My assumption (and I could be wrong) is that the layers approach isn't clear as you might have not used PGraphics before.
However, this option Kevin provided is simple and straight forward:
multiplying the coordinates manually
Notice most drawing functions take not only the coordinates, but also dimensions ?
Don't use scale(), but keep track of a multiplier floating point variable that you use for the shape dimensions. Manually scale the dimensions of each shape:
void draw(){
//map mouseX to a scale between 10% and 300%
float scale = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(255);
//scale the shape dimensions, without using scale()
ellipse(50,50, 30 * scale, 30 * scale);
}
You can run this as a demo bellow:
function setup(){
createCanvas(100,100);
}
function draw(){
//map mouseX to a scale between 10% and 300%
var scale = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(200);
//scale the shape dimensions, without using scale()
ellipse(50,50, 30 * scale, 30 * scale);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.min.js"></script>
Another answer is in the question itself: what value would you feed to strokeWidth() ? If scale() is making the stroke bigger, but you want to keep it's appearance the same, that means you need to use a smaller stroke weight as scale increases: the thickness is inversely proportional to the scale:
void draw(){
//map mouseX to a scale between 10% and 300%
float scale = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(255);
translate(50,50);
scale(scale);
strokeWeight(1/scale);
//scaled shape, same appearing stroke, just smaller in value as scale increases
ellipse(0,0, 30, 30);
}
You can run this bellow:
function setup(){
createCanvas(100,100);
}
function draw(){
//map mouseX to a scale between 10% and 300%
var scaleValue = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(240);
translate(50,50);
scale(scaleValue);
strokeWeight(1/scaleValue);
//scale the shape dimensions, without using scale()
ellipse(0,0, 30, 30);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.min.js"></script>
Kevin was patient, not only to answer your question, but also your comments, being generous with his time. You need to be patient to carefully read and understand the answers provided. Try it on your own then come back with specific questions on clarifications if that's the case. It's the best way to learn.
Simply asking "how do I do this ?" without showing what you're tried and what your thinking behind the problem is, expecting a snippet to copy/paste will not get your very far and this is not what stackoverflow is about.
You'll have way more to gain by learning, using the available documentation and especially thinking about the problem on your own first. You might not crack the problem at the first go (I know I certainly don't), but reasoning about it and viewing it from different angles will get your gears going.
Always be patient, it will serve you well on the long run, regardless of the situation.
Update Perhaps you mean by
What value fed to strokeWidth() will give a stroke width of one pixel regardless of the current scale() setting?
is how can you draw without anti-aliasing ?
If so, you can disable smoothing via a line: calling noSmooth(); once in setup(). Try it with the example code above.
None.
The whole point of scale() is that it, well, scales everything.
You might want to draw things in layers: draw one scaled layer, and one unscaled layer that contains the single-pixel-width lines. Then combine those layers.
That won't work if you need your layers to be mixed, such as an unscaled line on top of a scaled shape, on top of another scaled line. In that case you'll just have to unscale before drawing your lines, then scale again to draw your shapes.
Related
how would a go about drawing the inner blue slice of this circle, to simulate varying stroke weight.
I have tried a approach where i draw the stroke by drawing small circles on each angle of the circle and increasing the radius on certain parts of the circle. But this doesnt give the right result because the circle gets "pixelated" in the edge, and it skews the circle outwards.
There is no easy way to accomplish this. Part of the difficulty is that Canvas, the underlying technology that p5.js uses to draw graphics, doesn't support variable stroke weights either. In Scalable Vector Graphics, which has similar limitations, the best way to accomplish this would be to describe the shape as the outer perimeter, and the perimeter of the inner void, and then fill the shape without any stroke. I think Canvas would support this approach, but I don't think it can be done easily with p5.js because there's now way to jump to a new position when drawing bezier curves with beginShape()/bezierVertex(). However, one way you could do this in p5.js would be to fill the outer shape and then "remove" the inner void. If you want to draw this on top of other existing graphics then the best way is to draw this shape to a separate p5.Graphics object which you then draw to your main canvas with image():
let sprite;
function setup() {
createCanvas(windowWidth, windowHeight);
sprite = createGraphics(100, 100);
sprite.noStroke();
sprite.fill('black');
sprite.angleMode(DEGREES);
sprite.circle(50, 50, 100);
// switch to removing elements from the graphics
sprite.erase();
// Translate and rotate to match the shape you showed in your question
sprite.translate(50, 50);
sprite.rotate(-45);
// Remove a perfect semi circle from one half, producing regular 5px stroke circle
sprite.arc(0, 0, 90, 90, -90, 90);
// Remove a half-ellipse from the other side of the circle, but this time the
// height matches the previous arc, but the width is narrower.
// Note: the angles for this arc overlap the previous removal by a few degrees
// to prevent there from being a visible seam in between the two removed shapes.
sprite.arc(0, 0, 70, 90, 85, 275, OPEN);
}
function draw() {
background('lightgray');
image(sprite, mouseX - 50, mouseY - 50);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Repeatedly drawing a semi-opaque black rectangle over the entire canvas before each animation frame is an easy way to get an afterimage effect for moving shapes and it gives me exactly what I need - up to a point. With too slow a fade it doesn't fade all the way to black. Here's an example:
var canv = document.createElement('canvas');
document.body.appendChild(canv);
var ctx = canv.getContext('2d');
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, 100, 100);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(20, 20, 60, 60);
window.requestAnimationFrame(doFade);
function doFade() {
// Never fades away completely
ctx.fillStyle = 'rgba(0, 0, 0, 0.02)';
ctx.fillRect(20, 20, 60, 60);
window.requestAnimationFrame(doFade);
}
jsfiddle
This looks to me like a numeric precision problem - you can't expect the canvas to keep floating point pixel values around - but I'm not sure how to get around this.
I tried reading the image into a pattern, blanking the canvas, and then filling with the pattern at lower opacity in the hope that I could make rounding error work in my favor, but it seems to have the same result.
Short of reading out the image data and setting to black any pixels below a certain threshold, which would be prohibitively slow, I'm running out of ideas and could use some suggestions.
Thanks!
I thought I'd share my solution for the benefit of anyone else who might run into this problem. I was hoping to avoid doing any pixel-level manipulation, but beyond a certain threshold it's just not possible with the built-in canvas operations because the underlying bitmap is only 8 bits per channel and small fades will work out to less than one least significant bit and won't have any effect on the image data.
My solution was to create an array representing the age of each pixel. After each frame is drawn, I scan the imageData array, looking only at the alpha channel. If the alpha is 255 I know the pixel has just been written, so I set the age to 0 and set the alpha to 254. For any other non-zero alpha values, I increment the pixel age and then set the new alpha based on the pixel age.
The mapping of pixel age to alpha value is done with a lookup table that's populated when the fade rate is set. This lets me use whatever decay curve I want without extra math during the rendering loop.
The CPU utilization is a bit higher, but it's not too much of a performance hit and it can do smooth fades over several seconds and always fades entirely to black eventually.
I'm generating a falloff texture by adding gradient part to the white image I have. If implementation is relevant, I'm doing it with HTML5 canvas. For some reason I'm getting weird ray like while artifacts where it's supposed to be gradient smooth. I couldn't find any way to take care of that on implementation level, so I have to get rid of them after generating. Question is, if I have per pixel access to the image, how do I recognize those white pixels and replace with pixels to keep the gradient smooth?
The rays are caused by overlaps and rounding errors. They can be removed or at least reduced by using a Gaussian blur filter (which in effect act as a low-pass filter).
To avoid new problems such as the inner shape's black pixels leaking into the gradient, I'd suggest these steps:
Fill inner shape in the same color as the start color of the gradient.
Produce gradients
Apply Gaussian blur using either the filter property of context (f.ex context.filter = "blur(7px)";, reset by setting it to none), or by using a manual implementation
Redraw the inner shape in the destination color.
Now it's a simple matter of experimenting with the blur radius to find an optimal value. Note that blurring will add to the gradient so you might want to link the two so that the radius of the gradient is reduced when blur radius is increased.
Pro-tip: you can also drop the gradient production all together and simply make the glow effect using Gaussian blur (run example below).
var ctx = c.getContext("2d");
ctx.moveTo(300, 50);
ctx.quadraticCurveTo(325, 300, 550, 550);
ctx.quadraticCurveTo(300, 500, 50, 550);
ctx.quadraticCurveTo(250, 300, 300, 50);
ctx.closePath();
// blur next drawings
ctx.filter = "blur(20px)"; // glow radius
// produce a full base using fill and heavy stroke
ctx.fillStyle = ctx.strokeStyle = "#fff";
ctx.fill();
ctx.lineWidth = 40; // thicker = stronger spread
ctx.stroke();
// final, fill center in destination color
ctx.filter = "none";
ctx.fillStyle = "#000";
ctx.fill();
#c {background:#000}
<canvas id=c width=600 height=600></canvas>
This have been driving me crazy for the past couple of days.
I'm animating a spritesheet, and it actually works out fine on my 96px 384px texture with this code:
glBegin(GL_QUADS);
glTexCoord2f((frameCount*24.0f)/imgWidth, (row*24.0f)/imgHeight); glVertex3f(0+x, 0+y, -0.001f*(y+32));
glTexCoord2f((frameCount*24.0f)/imgWidth, ((row+1)*24.0f)/imgHeight); glVertex3f(0+x, 32+y, -0.001f*(y+32));
glTexCoord2f(((frameCount+1)*24.0f)/imgWidth, ((row+1)*24.0f)/imgHeight); glVertex3f(32+x, 32+y, -0.001f*(y+32));
glTexCoord2f(((frameCount+1)*24.0f)/imgWidth, (row*24.0f)/imgHeight); glVertex3f(32+x, 0+y, -0.001f*(y+32));
glEnd();
Problem is though, that when I load in a 32px 32px texture, it looks weird! I suspect that the number 24.0f should be different according to the texture size, but I can't figure out how.
Second question: How does this method affect the performance, are there better ways of doing it?
The texture coordinate for the x-axis (width or u value) should be:
frameCount * (frameWidth / imgWidth)
with frameWidth being the width of each frame in your texture and imgWidth being the total width of the texture.
The texture coordinate for the y-axis (height or v value) should be:
frameCount * (frameHeight / imgHeight)
with frameHeight being the height of each frame in your texture and imgHeight being the total height of the texture (in this case they are probably the same since each frame texture has same height as the entire texture here - or that's what I'm assuming by looking at your code).
If you want the code to be more efficient, you can precompute the multiplications that happen multiple times for each quad. So you can probably precompute:
float widthFraction = frameWidth / imgWidth;
float heightFraction = frameHeight / imgHeight;
The same applies for the vertex coordinate calculations, by the way.
Over hundreds of thousands of vertices, this will definitely speed the computations up a bit, but you should compare the two methods to see how much.
I'm making a game in 3D. Everything is correct in my code, although I'm confused about one thing.
When I setting up my perspective (gluPerspective) I set it to zNear = 0.1f and zFar = 100.0f. So far so good. Now, I also wanna move things just in the x or y direction via glTranslate.... But, the origo starts in the absolute centrum of my screen. Like I have zFar and zNear, why isn't that properly to the x and y coordinates? Now it is like if I move my sprite -2.0f to left on x-axis and make glTranslate... to handle that, it almost out of screen. And the z-axis is not behave like that. That's make it a lot more difficult to handle calculations in all directions. It's quite hard to add an unique float value to an object and for now I just add these randomly to just make them stay inside screen.
So, I have problem calculate corrects value to each object. Have I missed something? Should I change or thinkig of something? The reason that this is important is because I need to know the absolute left and right of my screen to make these calculations.
This is my onSurfaceChanged:
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluPerspective(gl, 45.0f, (float)width / (float)height,
0.1f, 100.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
Thanks in advance!
When you use gluPerspective you are transforming your coordinates from 3D world space into 2D screen space, using a matrix which looks at (0,0,0) by default (i.e. x= 0, y = 0 is in the center of the screen). When you set your object coordinates you are doing it in world space, NOT screen space.
If you want to effectively do 2D graphics (where things are given coordinates respective to their position on the screen you want to use gluOrtho2D instead.