Most of You interested in Raycasting probably know the Lodev and Permadi tutorials:
https://lodev.org/cgtutor/raycasting2.html
https://permadi.com/1996/05/ray-casting-tutorial-11/
At first I implemented so called "vertical floor/ceiling" raycast, it continues drawig column by column wall routine, it just starst drawing floors when the wall is done, that optimized thinking, but the algorithm itself is very, very slow.
So I tried Lodevs "horizontal floor/ceiling" raycast and it was huuuuge difference and speed up..
everything would be OK, but this algorithm, despite that is fast, wastes performance on filling up the
whole screen with floor and ceiling, and after that it draws walls.
I would like to optimize that feature, so the floor and ceiiling would be drawn after walls are drawn and fill only the empty spaces.
Maybe the solution would be to remember blank spaces during wall casting, and then create array containing that x, y coords, so during floor and ceil casting we already know where to draw.. what do you think. Do you know better aproaches, maybe some hints, learing sources, algorithms? Thanks in advance...
ps. I am using mouse to look around, so the horizon is changing.
I am developing on Windows but pararell I am porting my code to faster Amigas with m68k 060/080 cpus with RTG in 320x240x32 or 640x480x32.. I got nice results so far.. so trying to optimize az much as I can everything.
Below some of my tests, and progresses...
PC <-> AMIGA (WIN UAE)
https://www.youtube.com/watch?v=hcFBPfDYZig
AMIGA, V600 080/78 Mhz - 320x240x32 no textures (sorry for quality)
https://www.youtube.com/watch?v=6dv46hT1A_Y
Since the question is not related to any language, I answer from a Javascript perspective.
I implemented the so called "vertical floor/ceiling" technique as well.
But instead of drawing pixels per pixel with ctx.drawImage() I use putImageData.
First I get the data from the tiles I want to render using a temporary canvas:
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = 64;
tempCanvas.height = 64;
var wallsSprite = new Image();
wallsSprite.onload = function () {
tempCtx.drawImage(wallsSprite, 0, 128, 64, 64, 0, 0, 64, 64);
floorData = tempCtx.getImageData(0, 0, 64, 64);
tempCtx.drawImage(wallsSprite, 0, 192, 64, 64, 0, 0, 64, 64);
ceilData = tempCtx.getImageData(0, 0, 64, 64);
}
wallsSprite.src = "./walls_2.png";
I create an empty imageData:
var floorSprite = this.ctx.createImageData(600, 400);
Then I do my "vertical floor/ceiling" raycasting:
//we check if the wall reaches the bottom of the canvas
// this.wallToBorder = (400 - wallHeight) / 2;
if (this.wallToBorder > 0) {
// we calculate how many pixels we have from bottom of wall to border of canvas
var pixelsToBottom = Math.floor(this.wallToBorder);
//we calculate the distance between the first pixel at the bottom of the wall and the player eyes (canvas.height / 2)
var pixelRowHeight = 200 - pixelsToBottom;
// then we loop through every pixels until we reach the border of the canvas
for (let i = pixelRowHeight; i < 200; i += 1) {
// we calculate the straight distance between the player and the pixel
var directDistFloor = (this.screenDist * 200) / (Math.floor(i));
// we calculate it's real world distance with the angle relative to the player
var realDistance = (directDistFloor / Math.cos(this.angleR));
// we calculate it's real world coordinates with the player angle
this.floorPointx = this.player.x + Math.cos(this.angle) * realDistance / (this.screenDist / 100);
this.floorPointy = this.player.y + Math.sin(this.angle) * realDistance / (this.screenDist / 100);
// we map the texture
var textY = Math.floor(this.floorPointx % 64);
var textX = Math.floor(this.floorPointy % 64);
// we modify floorSprite array:
if (floorData && ceilData) {
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600] = floorData.data[textY * 4 * 64 + textX * 4]
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 1] = floorData.data[textY * 4 * 64 + textX * 4 + 1]
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 2] = floorData.data[textY * 4 * 64 + textX * 4 + 2]
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 3] = 255;
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600] = ceilData.data[textY * 4 * 64 + textX * 4]
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 1] = ceilData.data[textY * 4 * 64 + textX * 4 + 1]
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 2] = ceilData.data[textY * 4 * 64 + textX * 4 + 2]
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 3] = 255;
}
}
}
}
}
finally we draw the floor and ceiling before the walls are rendered:
this.ctx.putImageData(floorSprite, 0, 0);
The result is super fast since:
we don't need to calculate ceiling texture coordinates since we deduce them from the floor coordinates.
we draw the ceiling/floor only once per loop, not pixels per pixel.
only the pixels that are visible are redrawn so it doesn't wastes performance on filling up the whole screen with floor and ceiling, and after that it draws walls.
Maybe it could be optimized with mixing horizontal raysting and putImageData put the game speed with wall/ceiling rendering or without is almost the same.
Here is the result
Related
I am trying to place evenly-spaced markers/dots on a quadratic curve drawn with HTML Canvas API. Found a nice article explaining how the paths are calculated in the first place, at determining coordinates on canvas curve.
There is a formula, at the end, to calculate the angle:
function getQuadraticAngle(t, sx, sy, cp1x, cp1y, ex, ey) {
var dx = 2*(1-t)*(cp1x-sx) + 2*t*(ex-cp1x);
var dy = 2*(1-t)*(cp1y-sy) + 2*t*(ey-cp1y);
return Math.PI / 2 - Math.atan2(dx, dy);
}
The x/y pairs that we pass, are the current position, the control point and the end position of the curve - exactly what is needed to pass to the canvas context, and t is a value from 0 to 1. Unless I somehow misunderstood the referenced article.
I want to do something very similar - place my markers over the distance specified s, rather than use t. This means, unless I am mistaken, that I need to calculate the length of the "curved path" and from there, I could probably use the above formula.
I found a solution for the length in JavaScript at length of quadratic curve. The formula is similar to:
.
And added the below function:
function quadraticBezierLength(x1, y1, x2, y2, x3, y3) {
let a, b, c, d, e, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = a = 4 * (d * d + d1 * d1);
c1 += b = 4 * (d * e + d1 * e1);
c1 += c = e * e + e1 * e1;
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (
(a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) /
(4 * a1)
);
}
Now, I am trying to space markers evenly. I thought that making "entropy" smooth - dividing the total length by the step length would result in the n markers, so going using the 1/nth step over t would do the trick. However, this does not work. The correlation between t and distance on the curve is not linear.
How do I solve the equation "backwards" - knowing the control point, the start, and the length of the curved path, calculate the end-point?
Not sure I fully understand what you mean by "space markers evenly" but I do have some code that I did with curves and markers that maybe can help you ...
Run the code below, it should output a canvas like this:
function drawBezierCurve(p0, p1, p2, p3) {
distance = 0
last = null
for (let t = 0; t <= 1; t += 0.0001) {
const x = Math.pow(1 - t, 3) * p0[0] + 3 * Math.pow(1 - t, 2) * t * p1[0] + 3 * (1 - t) * Math.pow(t, 2) * p2[0] + Math.pow(t, 3) * p3[0];
const y = Math.pow(1 - t, 3) * p0[1] + 3 * Math.pow(1 - t, 2) * t * p1[1] + 3 * (1 - t) * Math.pow(t, 2) * p2[1] + Math.pow(t, 3) * p3[1];
ctx.lineTo(x, y);
if (last) {
distance += Math.sqrt((x - last[0]) ** 2 + (y - last[1]) ** 2)
if (distance >= 30) {
ctx.rect(x - 1, y - 1, 2, 2);
distance = 0
}
}
last = [x, y]
}
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
drawBezierCurve([0, 0], [40, 300], [200, -90], [300, 150]);
ctx.stroke();
<canvas id="canvas" width=300 height=150></canvas>
I created the drawBezierCurve function, there I'm using a parametric equation of a bezier curve and then I use lineTo to draw between points, and also we get a distance between points, the points are very close so my thinking is OK to use the Pythagorean theorem to calculate the distance, and the markers are just little rectangles.
I have an xp system and I have the percentage of how far someone is to leveling up, and I created a rectangle. but I don't know how to change the width depending on the percentage...
I have the percentage easily, but I need a way to code that percentage 0 = start point of 259, and 100 percent is 630
const rectX = 259;
const rectY = 182;
const rectWidth = 630;
const rectHeight = 38;
const cornerRadius = 20;
ctx.lineJoin = 'round';
ctx.lineWidth = cornerRadius;
ctx.strokeStyle = '#FF1700';
ctx.fillStyle = '#FF1700';
ctx.strokeRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);
ctx.fillRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);
You just need to multiply the width of the rectangle with percentage / 100. Here is how it would look with your sample code.
const rectX = 259;
const rectY = 182;
const rectWidth = 630;
const rectHeight = 38;
const cornerRadius = 20;
const scale = Math.min(1, Math.max(0, percentage / 100));
ctx.lineJoin = 'round';
ctx.lineWidth = cornerRadius;
ctx.strokeStyle = '#FF1700';
ctx.fillStyle = '#FF1700';
ctx.strokeRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);
ctx.fillRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth * scale - cornerRadius , rectHeight - cornerRadius);
When percentage is 100% scale will be 1 and the width of the rectangle will result in 630. When percentage is 50% scale will be 0.5 and the width will be 315 and so on.
So from what I understand you have two numbers, starts at at 259 and ends at 630, and 50% would mean the midpoint between these two numbers, 100% would mean 630 and 0% would mean 259, is that correct?
If that's the case, then you need this thing called LERP (linear interpolation),
const lerp = (start: number, end: number, percentage: number): number => {
return (((end - start) * percentage) / 100) + start;
};
OR if you want javascript, simply remove the type definitions,
The way it works is by normalizing your start to 0,
Now using this is quite simple: console.log(lerp(259, 630, 50))
which returns: 444.5
I would like to draw a circle with a diameter of 15 into a matrix of 15 by 15.
For this I tried the OpenCv circle function and the shift function.
I am not sure whether I use the function in a correct way or wheather it is not possible what I would like to do.
The best I achieve is the following unsymmetric circle with a size of 16 pixels:
How can I get a symmetric circle with a diameter of 15 pixels?
The code I used:
import cv2
import numpy as np
circle_diameter = 15
circular_mask = np.zeros((circle_diameter, circle_diameter, 1), dtype='uint8')
#Draw circle with subpixel accuracy
shift = 4
factor = (1 << shift)
cv2.circle(circular_mask, (int(round((circle_diameter/2) * factor)), int(round((circle_diameter/2) * factor))), int(round((circle_diameter/2) * factor)), (255), -1, shift=shift)
circular_mask = cv2.resize(circular_mask,None,fx=5,fy=5)
cv2.imshow("mask", circular_mask)
Thanx
Ok, here you go.
I'll write in C++ syntax, but should be same for Python.
It seems like the pixel coordinates in cv::circle refers to the pixel center.
cv::Mat img = cv::Mat::zeros(15, 15, CV_8UC1);
// in this code, shift must be >= 1 because of the later line (1<<(shift-1)) to add the .5 for an arbitrary shift size
const int shift = 2;
int radiusLow = 7;
int radiusShift = (radiusLow << shift) + (1<<(shift-1)); // + .5
//int x = (7 << shift) + (1<<(shift-1)); // wrong, because the pixel position seems to be the pixel center already. 7.5 would be the right ede of the pixel
//int y = (7 << shift) + (1<<(shift-1)); // wrong, because the pixel position seems to be the pixel center already. 7.5 would be the right ede of the pixel
int x = 7<<shift;
int y = 7<<shift;
cv::circle(img, cv::Point(x, y), radiusShift, cv::Scalar::all(255), -1, 8, shift);
//cv::resize(img, img, cv::Size(), 50, 50); // for visualization
cv::imshow("img", img);
cv::waitKey(0);
But the result seems to have some pixel disrectization problems, though looks like as beeing centered and with 7.5 radius.
Result is resized for visualization.
Same code (but smaller resize factor) with radius 6.5 gives this image (looks like some rounding fragments during drawing).
Another test, using more bits to represent a number close to 7.5 radius, but a few bits smaller, to reduce rounding fragments in drawing:
cv::Mat img = cv::Mat::zeros(17, 17, CV_8UC1); // 2 pixels bigger for visualization of possible artifacts
const int shift = 5; // more bits for fraction
int radiusLow = 7;
int radiusShift = (radiusLow << shift) + (1<<(shift-1)) -1; // 7+ 2^-1 - 2^-5
// center of the 17x17 image
int x = 8<<shift;
int y = 8<<shift;
Mickas Code in Python:
import cv2
import numpy as np
circle_diameter = 15
circular_mask = np.zeros((circle_diameter+2, circle_diameter+2, 1), dtype='uint8') #Plus two for visualization
shift = 5
radius_low = np.floor(circle_diameter/2)
radius_shifted = (np.uint64(radius_low) << np.uint64(shift)) + (np.uint64(1)<<np.uint64((shift-1))) -1; # 7+ 2^-1 - 2^-5
circle_x = 8 << shift
circle_y = 8 << shift
cv2.circle(circular_mask, (circle_x,circle_y), int(radius_shifted), (255), -1, shift=shift)
circular_mask = cv2.resize(circular_mask,None,fx=5,fy=5)
cv2.imshow("mask", circular_mask)
What I am doing now is, I am getting all pixels with var getPixels = require("get-pixels"), then I am looping over the array of pixels with this code:
var pixelation = 10;
var imageData = ctx.getImageData(0, 0, img.width, img.height);
var data = imageData.data;
for(var y = 0; y < sourceHeight; y += pixelation) {
for(var x = 0; x < sourceWidth; x += pixelation) {
var red = data[((sourceWidth * y) + x) * 4];
var green = data[((sourceWidth * y) + x) * 4 + 1];
var blue = data[((sourceWidth * y) + x) * 4 + 2];
//Assign
for(var n = 0; n < pixelation; n++) {
for(var m = 0; m < pixelation; m++) {
if(x + m < sourceWidth) {
data[((sourceWidth * (y + n)) + (x + m)) * 4] = red;
data[((sourceWidth * (y + n)) + (x + m)) * 4 + 1] = green;
data[((sourceWidth * (y + n)) + (x + m)) * 4 + 2] = blue;
}
}
}
}
}
The problem with this method is that the result image is too sharp.
What I am looking for is something, similar to this one which has been done with ImageMagick -sale
The command which I've used for the second one is
convert -normalize -scale 10% -scale 1000% base.jpg base2.jpg
the problem with this method is that I don't know how to specify the actual pixel size.
So is it possible to get the second result with that for loop. Or is better to use imagemagick -scale but if some one can help with the math, so I can have actual pixel size would be great.
Not sure what maths you are struggling with, but if we start with a 600x600 image like this:
Then, if you want the final image to have just 5 blocky pixels across and 5 blocky pixels down the page, you can scale it down to 5x5 and then scale it back up to its original size:
convert start.png -scale 5x5 -scale 600x600 result.png
Or, if you want to go to 10x10 blocky pixels:
convert start.png -scale 10x10 -scale 600x600 result2.png
The way you've written this, the image is processed into pixels of size nxn where n is specified by your pixelation variable. Increasing pixelation will provide the desired "coarseness".
I have an image which 330 px width and 472 px height . I want to plot this image inn to a bigger canvas which is 55% bigger than the current picture .. How can I fount the final image width and height .
This is more of a math problem than a programming problem. Let's work on it abstractly to get a formula:
Our variables are:
oldWidth, oldHeight, newWidth, newHeight, percentBigger
The original canvas size is
oldCanvasSize = oldWidth * oldHeight
The new canvas size is
newCanvasSize = newWidth + newHeight
55% bigger means 155% or 1.55 so
percent = (percentBigger + 1)
The new canvas is some percent of the old canvas so we have
newCanvasSize = percent * oldCanvasSize
or
newCanvasSize = (percentBigger + 1) * oldCanvasSize
I'll assume we want the proportions to stay the same so
oldWidth / oldHeight = newWidth / newHeight
By substitution and solving the above equations we get
newWidth = SquareRoot(percentBigger + 1) * oldWidth
newHeight = SquareRoot(percentBigger + 1) * oldHeight
I'll let the reader plug in the values.