Bilinear Interpolation, something wrong with my implementation - c++

I am trying to implement a bilinear interpolation function, but for some reason I am getting bad output. I cant seem to figure out what's wrong, any help getting on the right track will be appreciated.
double lerp(double c1, double c2, double v1, double v2, double x)
{
if( (v1==v2) ) return c1;
double inc = ((c2-c1)/(v2 - v1)) * (x - v1);
double val = c1 + inc;
return val;
};
void bilinearInterpolate(int width, int height)
{
// if the current size is the same, do nothing
if(width == GetWidth() && height == GetHeight())
return;
//Create a new image
std::unique_ptr<Image2D> image(new Image2D(width, height));
// x and y ratios
double rx = (double)(GetWidth()) / (double)(image->GetWidth()); // oldWidth / newWidth
double ry = (double)(GetHeight()) / (double)(image->GetHeight()); // oldWidth / newWidth
// loop through destination image
for(int y=0; y<height; ++y)
{
for(int x=0; x<width; ++x)
{
double sx = x * rx;
double sy = y * ry;
uint xl = std::floor(sx);
uint xr = std::floor(sx + 1);
uint yt = std::floor(sy);
uint yb = std::floor(sy + 1);
for (uint d = 0; d < image->GetDepth(); ++d)
{
uchar tl = GetData(xl, yt, d);
uchar tr = GetData(xr, yt, d);
uchar bl = GetData(xl, yb, d);
uchar br = GetData(xr, yb, d);
double t = lerp(tl, tr, xl, xr, sx);
double b = lerp(bl, br, xl, xr, sx);
double m = lerp(t, b, yt, yb, sy);
uchar val = std::floor(m + 0.5);
image->SetData(x,y,d,val);
}
}
}
//Cleanup
mWidth = width; mHeight = height;
std::swap(image->mData, mData);
}
Input Image (4 pixels wide and high)
My Output
Expected Output (Photoshop's Bilinear Interpolation)

Photoshop's algorithm assumes that each source pixel's color is in the center of the pixel, while your algorithm assumes that the color is in its topleft. This causes your results to be shifted half a pixel up and left compared to Photoshop.
Another way to look at it is that your algorithm maps the x coordinate range (0, srcWidth) to (0, dstWidth), while Photoshop maps (-0.5, srcWidth-0.5) to (-0.5, dstWidth-0.5), and the same in y coordinate.
Instead of:
double sx = x * rx;
double sy = y * ry;
You can use:
double sx = (x + 0.5) * rx - 0.5;
double sy = (y + 0.5) * ry - 0.5;
to get similar results. Note that this can give you a negative value for sx and sy.

Related

Panorama to Tiny Planet in OpenCV C++

I tried to convert panorama to tiny planet using C++ and OpenCV but the image result is noisy. I am not really sure which part I did wrong. I think it has something to do with color.
I tried to convert panorama to tiny planet using C++ and OpenCV but the image result is noisy. I am not really sure which part I did wrong. I think it has something to do with color.
Tutorial I referred to
http://codeofthedamned.com/index.php/the-little-planet-effect
Panorama source
Tiny image result
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import "OpenCVWrapper.h"
using namespace cv;
#implementation OpenCVWrapper
+ (UIImage*)createTinyPlanetFromImage: (UIImage*)image {
Mat pano;
UIImageToMat(image, pano);
Mat grayMat;
RenderProjection(pano, 1000.0, grayMat);
return MatToUIImage(grayMat);
}
void RenderProjection(Mat &pano, long len, Mat &output) {
const double k_pi = 3.1415926535897932384626433832795;
const double k_pi_inverse = 0.31830988618379067153776752674503;
output.create(len, len, CV_16UC3);
long half_len = len / 2;
cv::Size sz = pano.size();
for (long indexX = 0; indexX < len; ++indexX) {
for (long indexY = 0; indexY < len; ++indexY) {
double sphereX = (indexX - half_len) * 10.0 / len;
double sphereY = (indexY - half_len) * 10.0 / len;
double Qx, Qy, Qz;
if (GetIntersection(sphereX, sphereY, Qx, Qy, Qz)) {
double theta = std::acos(Qz);
double phi = std::atan2(Qy, Qx) + k_pi;
theta = theta * k_pi_inverse;
phi = phi * (0.5 * k_pi_inverse);
double Sx = min(sz.width -2.0, sz.width * phi);
double Sy = min(sz.height-2.0, sz.height * theta);
output.at<Vec3s>(int(indexY), int(indexX)) = BilinearSample(pano, Sx, Sy);
}
}
}
}
bool GetIntersection(double u, double v, double &x, double &y, double &z) {
double Nx = 0.0;
double Ny = 0.0;
double Nz = 1.0;
double dir_x = u - Nx;
double dir_y = v - Ny;
double dir_z = -1.0 - Nz;
double a = (dir_x * dir_x) + (dir_y * dir_y) + (dir_z * dir_z);
double b = (dir_x * Nx) + (dir_y * Ny) + (dir_z * Nz);
b *= 2;
double d = b * b;
double q = -0.5 * (b - std::sqrt(d));
double t = q / a;
x = (dir_x * t) + Nx;
y = (dir_y * t) + Ny;
z = (dir_z * t) + Nz;
return true;
}
Vec3s BilinearSample(Mat &image, double x, double y) {
Vec3s c00 = image.at<Vec3s>(int(y), int(x));
Vec3s c01 = image.at<Vec3s>(int(y), int(x) + 1);
Vec3s c10 = image.at<Vec3s>(int(y) + 1, int(x));
Vec3s c11 = image.at<Vec3s>(int(y) + 1, int(x) + 1);
double X0 = x - floor(x);
double X1 = 1.0 - X0;
double Y0 = y - floor(y);
double Y1 = 1.0 - Y0;
double w00 = X0 * Y0;
double w01 = X1 * Y0;
double w10 = X0 * Y1;
double w11 = X1 * Y1;
short r = short(c00[2] * w00 + c01[2] * w01
+ c10[2] * w10 + c11[2] * w11);
short g = short(c00[1] * w00 + c01[1] * w01
+ c10[1] * w10 + c11[1] * w11);
short b = short(c00[0] * w00 + c01[0] * w01
+ c10[0] * w10 + c11[0] * w11);
return make_BGR(b, g, r);
}
Vec3s make_BGR(short blue, short green, short red) {
Vec3s result;
result[0] = blue;
result[1] = green;
result[2] = red;
return result;
}
#end
There problem solved when I replaced UIImageToMat(image, pano); and MatToUIImage(grayMat); with this code and we can remove this header #import <opencv2/imgcodecs/ios.h>
static void UIImageToMat2(UIImage *image, cv::Mat &mat) {
assert(image.size.width > 0 && image.size.height > 0);
assert(image.CGImage != nil || image.CIImage != nil);
// Create a pixel buffer.
NSInteger width = image.size.width;
NSInteger height = image.size.height;
cv::Mat mat8uc4 = cv::Mat((int)height, (int)width, CV_8UC4);
// Draw all pixels to the buffer.
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (image.CGImage) {
// Render with using Core Graphics.
CGContextRef contextRef = CGBitmapContextCreate(mat8uc4.data, mat8uc4.cols, mat8uc4.rows, 8, mat8uc4.step, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault);
CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), image.CGImage);
CGContextRelease(contextRef);
} else {
// Render with using Core Image.
static CIContext* context = nil; // I do not like this declaration contains 'static'. But it is for performance.
if (!context) {
context = [CIContext contextWithOptions:#{ kCIContextUseSoftwareRenderer: #NO }];
}
CGRect bounds = CGRectMake(0, 0, width, height);
[context render:image.CIImage toBitmap:mat8uc4.data rowBytes:mat8uc4.step bounds:bounds format:kCIFormatRGBA8 colorSpace:colorSpace];
}
CGColorSpaceRelease(colorSpace);
// Adjust byte order of pixel.
cv::Mat mat8uc3 = cv::Mat((int)width, (int)height, CV_8UC3);
cv::cvtColor(mat8uc4, mat8uc3, cv::COLOR_RGBA2BGR);
mat = mat8uc3;
}
and
static UIImage *MatToUIImage2(cv::Mat &mat) {
// Create a pixel buffer.
assert(mat.elemSize() == 1 || mat.elemSize() == 3);
cv::Mat matrgb;
if (mat.elemSize() == 1) {
cv::cvtColor(mat, matrgb, cv::COLOR_GRAY2RGB);
} else if (mat.elemSize() == 3) {
cv::cvtColor(mat, matrgb, cv::COLOR_BGR2RGB);
}
// Change a image format.
NSData *data = [NSData dataWithBytes:matrgb.data length:(matrgb.elemSize() * matrgb.total())];
CGColorSpaceRef colorSpace;
if (matrgb.elemSize() == 1) {
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
CGImageRef imageRef = CGImageCreate(matrgb.cols, matrgb.rows, 8, 8 * matrgb.elemSize(), matrgb.step.p[0], colorSpace, kCGImageAlphaNone|kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return image;
}

Why are my openGL ellipses pointed?

I copied this ellipse code directly from the opengl textbook:
void ellipseMidpoint (int xCenter, int yCenter, int Rx, int Ry)
{
int Rx2 = Rx * Rx;
int Ry2 = Ry * Ry;
int twoRx2 = 2 * Rx2;
int twoRy2 = 2 * Ry2;
int p;
int x = 0;
int y = Ry;
int px = 0;
int py = twoRx2 * y;
//initial points in both quadrants
ellipsePlotPoints (xCenter, yCenter, x, y);
//Region 1
p = round (Ry2 - (Rx2 * Ry) + (0.25 * Rx2));
while (px < py) {
x++;
px += twoRy2;
if (p < 0)
p += Ry2 + px;
else {
y--;
py -= twoRx2;
p += Ry2 + px - py;
}
ellipsePlotPoints (xCenter, yCenter, x, y);
}
//Region 2
p = round (Ry2 * (x+0.5) * (x+0.5) + Rx2 * (y-1) * (y-1) - Rx2 * Ry2);
while (y > 0) {
y--;
py -= twoRx2;
if (p > 0)
p += Rx2 - py;
else {
x++;
px += twoRy2;
p += Rx2 - py + px;
}
ellipsePlotPoints (xCenter, yCenter, x, y);
}
}
void ellipsePlotPoints (int xCenter, int yCenter, int x, int y)
{
setPixel (xCenter + x, yCenter + y);
setPixel (xCenter - x, yCenter + y);
setPixel (xCenter + x, yCenter - y);
setPixel (xCenter - x, yCenter - y);
}
void setPixel (GLint xPos, GLint yPos)
{
glBegin (GL_POINTS);
glVertex2i(xPos, yPos);
glEnd();
}
The smaller ellipses seem to be fine but the larger ones are pointy and sort of flat at the ends.
Any ideas why?
Here is a current screenshot:
I think you're encountering overflow. I played with your code. While I never saw exactly the same "lemon" type shapes from your pictures, things definitely fell apart at large sizes, and it was caused by overflowing the range of the int variables used in the code.
For example, look at one of the first assignments:
int py = twoRx2 * y;
If you substitute, this becomes:
int py = 2 * Rx * Rx * Ry;
If you use a value of 1000 each for Rx and Ry, this is 2,000,000,000. Which is very close to the 2^31 - 1 top of the range of a 32-bit int.
If you want to use this algorithm for larger sizes, you could use 64-bit integer variables. Depending on your system, the type would be long or long long. Or more robustly, int64_t after including <stdint.h>.
Now, if all you want to do is draw an ellipsis with OpenGL, there are much better ways. The Bresenham type algorithms used in your code are ideal if you need to draw a curve pixel by pixel. But OpenGL is a higher level API, which knows how to render more complex primitives than just pixels. For a curve, you will most typically use a connected set of line segments to approximate the curve. OpenGL will then take care of turning those line segments into pixels.
The simplest way to draw an ellipsis is to directly apply the parametric representation. With phi an angle between 0 and PI, and using the naming from your code, the points on the ellipsis are:
x = xCenter + Rx * cos(phi)
y = yCenter + Ry * sin(phi)
You can use an increment for phi that meets your precision requirements, and the code will look something to generate an ellipsis approximated by DIV_COUNT points will look something like this:
float angInc = 2.0f * m_PI / (float)DIV_COUNT;
float ang = 0.0f;
glBegin(GL_LINE_LOOP);
for (int iDiv = 0; iDiv < DIV_COUNT; ++iDiv) {
ang += angInc;
float x = xCenter + Rx * cos(ang);
float y = yCenter + Ry * sin(ang);
glVertex2f(x, y);
glEnd();
If you care about efficiency, you can avoid calculating the trigonometric functions for each point, and apply an incremental rotation to calculate each point from the previous one:
float angInc = 2.0f * M_PI / (float)DIV_COUNT;
float cosInc = cos(angInc);
float sinInc = sin(angInc);
float cosAng = 1.0f;
float sinAng = 0.0f
glBegin(GL_LINE_LOOP);
for (int iDiv = 0; iDiv < DIV_COUNT; ++iDiv) {
float newCosAng = cosInc * cosAng - sinInc * sinAng;
sinAng = sinInc * cosAng + cosInc * sinAng;
cosAng = newCosAng;
float x = xCenter + Rx * cosAng;
float y = yCenter + Ry * sinAng;
glVertex2f(x, y);
glEnd();
This code is of course just for illustrating the math, and to get you started. In reality, you should use current OpenGL rendering methods, which includes vertex buffers, etc.

Scaling and rotating texture onto another texture by raw buffer data

I submitted this to gamedev, but they seem rather slow so I hope I could find an answer here.
I've been messing with C++ AMP and OGRE in attempt to make writing to/altering textures to my liking easier on my behalf. In this I've been trying to draw a texture onto my "dynamic" texture with strange results. It appears that a solid 3/4 of my image is cropped off and it's driving me mad as I cannot seem to find the fix.
Here's a video of the problem: http://www.youtube.com/watch?v=uFWxHtHtqAI
And here's all of the necessary code for the sake of understanding even though the kernel is really where the issue at hand rests:
DynamicTexture.h
#define ValidTexCoord(x, y, width, height) ((x) >= 0 && (x) < (width) && (y) >= 0 && (y) < (height))
void TextureKernel(array<uint32, 2> &buffer, array_view<uint32, 2> texture, uint32 x, uint32 y, Real rot, Real scale, bool alpha)
{
Real
c = cos(-rot) / scale,
s = sin(-rot) / scale;
int32
//e = int32(sqrt((texture.extent[1] * texture.extent[1]) + (texture.extent[0] * texture.extent[0])) * scale * 0.5F),
dx = texture.extent[1] / 2,
dy = texture.extent[0] / 2;
parallel_for_each(buffer.extent, [=, &buffer](index<2> idx) restrict(amp)
{
int32
tex_x = int32((Real(idx[1] - x) * c) - (Real(idx[0] - y) * s)) + dx,
tex_y = int32((Real(idx[1] - x) * s) + (Real(idx[0] - y) * c)) + dy;
if(ValidTexCoord(tex_x, tex_y, texture.extent[1], texture.extent[0]))
{
if(!alpha || (alpha && texture(tex_y, tex_x) != 0))
{
buffer(idx) = texture(tex_y, tex_x);
}
}
else
{
buffer(idx) = 0x336699FF;
}
});
}
template<typename T, int32 Rank>
void SetKernel(array<T, Rank> &arr, T val)
{
parallel_for_each(arr.extent, [&arr, val](index<Rank> idx) restrict(amp)
{
arr(idx) = val;
});
}
class DynamicTexture
{
static int32
id;
array<uint32, 2>
buffer;
public:
const int32
width,
height;
TexturePtr
textureptr;
DynamicTexture(const int32 width, const int32 height, uint32 color = 0) :
width(width),
height(height),
buffer(extent<2>(height, width))
{
SetKernel(buffer, color);
textureptr = TextureManager::getSingleton().createManual("DynamicTexture" + StringConverter::toString(++id), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TextureType::TEX_TYPE_2D, width, height, 0, PixelFormat::PF_A8R8G8B8);
}
~DynamicTexture()
{
}
void Texture(TexturePtr texture, uint32 x, uint32 y, Real rot = 0.F, Real scale = 1.F, bool alpha = false)
{
HardwarePixelBufferSharedPtr
pixelbuffer = texture->getBuffer();
TextureKernel(buffer, array_view<uint32, 2>(texture->getHeight(), texture->getWidth(), (uint32 *)pixelbuffer->lock(HardwareBuffer::HBL_READ_ONLY)), x, y, rot, scale, alpha);
pixelbuffer->unlock();
}
void CopyToBuffer()
{
HardwarePixelBufferSharedPtr
pixelbuffer = textureptr->getBuffer();
copy(buffer, stdext::make_checked_array_iterator<uint32 *>((uint32 *)pixelbuffer->lock(HardwareBuffer::HBL_DISCARD), width * height));
pixelbuffer->unlock();
}
void Reset(uint32 color)
{
SetKernel(buffer, color);
}
};
int32
DynamicTexture::id = 0;
main.cpp
void initScene()
{
dynamictexture = new DynamicTexture(window->getWidth(), window->getHeight());
TextureManager::getSingleton().load("minotaur.jpg", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TextureType::TEX_TYPE_2D, 0);
}
bool frameStarted(const FrameEvent &evt)
{
static Real
ang = 0.F;
ang += 0.05F;
if(ang > Math::TWO_PI)
{
ang = 0.F;
}
dynamictexture->Reset(0);
dynamictexture->Texture(TextureManager::getSingleton().getByName("minotaur.jpg"), dynamictexture->width / 2, dynamictexture->height / 2, ang, 4.F, true);
dynamictexture->CopyToBuffer();
return true;
}
As you can see, the dynamic texture is the size of the window (which in this case is 800x600) and the minotaur.jpg is 84x84. I'm simply placing it at half the width and height (center), rotating it by ang (radians), and scaling it to 4x.
In the kernel itself, I simply followed a 2D rotation matrix (where x and y are offset by the parameters 'x' and 'y'):
x' = x cosθ - y sinθ
y' = x sinθ + y cosθ
Also note that idx[1] represents the x value in the array and idx[0] represents the y because it's arranged in the manner that value = buffer[y + (x * height)] (or something along those lines, but just know it's in the correct format).
Thanks for any and all help!
Regards,
Tannz0rz
I found the solution thanks to this guy: https://sites.google.com/site/ofauckland/examples/rotating-pixels
const Real
HALF_PI = Math::HALF_PI;
const int32
cx = texture.extent[1] / 2,
cy = texture.extent[0] / 2;
parallel_for_each(buffer.extent, [=, &buffer](index<2> idx) restrict(amp)
{
int32
tex_x = idx[1] - x,
tex_y = idx[0] - y;
Real
dist = sqrt(Real((tex_x * tex_x) + (tex_y * tex_y))) / scale,
theta = atan2(Real(tex_y), Real(tex_x)) - angle - HALF_PI;
tex_x = int32(dist * sin(theta)) + cx;
tex_y = int32(dist * cos(theta)) + cy;
if(ValidTexCoord(tex_x, tex_y, texture.extent[1], texture.extent[0]))
{
buffer(idx) = texture(tex_y, tex_x);
}
});

In a directx application, find z (terrain) with known location

My english knowlage is not good enought to tell my problems. and i am using stackoverflow second time.
i am hooking a directx application, i just can wrote something to screen and get input from screen and other things.
This game has a terrain, and a lot of players. I can directly edit the player location (x, z, y). But when i edit x and z coordinate, the player is flying :) because i don't know how to calculate the y coordinate (terrain height), i can't calculate it.
Player coordinate is 700, 5.41, 600
when game edit it to 800 and 700, game makes y to 6.50
when i edit it to 800 and 700, the y coordinate still 5.41
6.50 is coordinate, height of terrain of (800, 700), 5.41 is 700,600 terrain height.
Is there a any way to get height of the terrain for speficed coordinate?
Thank you much more.
I found it.
Thanks to everyone.
The game is using N3Terrain :)
float CN3Terrain::GetHeight(float x, float z)
{
int ix, iz;
ix = ((int)x) / TILE_SIZE;
iz = ((int)z) / TILE_SIZE;
if(ix<0 || ix>(m_ti_MapSize-2)) return -FLT_MAX;
if(iz<0 || iz>(m_ti_MapSize-2)) return -FLT_MAX;
float dX, dZ;
dX = (x - (ix*TILE_SIZE)) / TILE_SIZE;
dZ = (z - (iz*TILE_SIZE)) / TILE_SIZE;
float y;
float h1, h2, h3, h12, h13;
if((ix+iz)%2==0) //»ç°¢ÇüÀÌ / ¸ð¾ç..
{
h1 = m_pMapData[ix*m_ti_MapSize + iz].fHeight;
h3 = m_pMapData[(ix+1)*m_ti_MapSize + (iz+1)].fHeight;
if (dZ > dX) //À­ÂÊ »ï°¢Çü..
{
h2 = m_pMapData[ix*m_ti_MapSize + (iz+1)].fHeight;
h12 = h1 + (h2-h1) * dZ; // h1°ú h2»çÀÌÀÇ ³ôÀÌ°ª
h13 = h1 + (h3-h1) * dZ; // h1°ú h3»çÀÌÀÇ ³ôÀÌ°ª
y = h12 + ((h13-h12) * (dX/dZ)); // ã°íÀÚ ÇÏ´Â ³ôÀÌ°ª
return y;
}
else //¾Æ·¡ÂÊ »ï°¢Çü..
{
if(dX==0.0f) return h1;
h2 = m_pMapData[(ix+1)*m_ti_MapSize + iz].fHeight;
h12 = h1 + (h2-h1) * dX; // h1°ú h2»çÀÌÀÇ ³ôÀÌ°ª
h13 = h1 + (h3-h1) * dX; // h1°ú h3»çÀÌÀÇ ³ôÀÌ°ª
y = h12 + ((h13-h12) * (dZ/dX)); // ã°íÀÚ ÇÏ´Â ³ôÀÌ°ª
return y;
}
}
else if ((ix+iz)%2==1) //»ç°¢ÇüÀÌ ¿ª½½·¹½¬ ¸ð¾ç..
{
h1 = m_pMapData[(ix+1)*m_ti_MapSize + iz].fHeight;
h3 = m_pMapData[ix*m_ti_MapSize + (iz+1)].fHeight;
if ((dX+dZ) > 1.0f) //À­ÂÊ »ï°¢Çü..
{
if(dZ==0.0f) return h1;
h2 = m_pMapData[(ix+1)*m_ti_MapSize + (iz+1)].fHeight;
h12 = h1 + (h2-h1) * dZ;
h13 = h1 + (h3-h1) * dZ;
y = h12 + ((h13-h12) * ((1.0f-dX)/dZ));
return y;
}
else //¾Æ·¡ÂÊ »ï°¢Çü..
{
if(dX==1.0f) return h1;
h2 = m_pMapData[ix*m_ti_MapSize + iz].fHeight;
h12 = h2+(h1-h2)*dX; // h1°ú h2»çÀÌÀÇ ³ôÀÌ°ª
h13 = h3+(h1-h3)*dX; // h1°ú h3»çÀÌÀÇ ³ôÀÌ°ª
y = h12 + ((h13-h12) * (dZ/(1.0f-dX)));
return y;
}
}
return -FLT_MAX;
}
One engine I used allowed you to cast rays and determine their intersection with objects. I found the "ground" by casting a ray from above aimed down and found the intersection with the terrain.
It works on Knight OnLine. its a wrapper to CN3Terrain::GetHeight(float x, float z).
float getY(float x, float z) {
__asm {
PUSH 0
PUSH z
PUSH x
MOV ECX,DWORD PTR DS:[0x0C26C20]
MOV ECX,DWORD PTR DS:[ECX+1Ch]
MOV EDX,DWORD PTR DS:[ECX]
CALL DWORD PTR DS:[EDX+34h]
}}

Separating Axis Theorem is driving me nuts!

i am working on an implementation of the Separting Axis Theorem for use in 2D games. It kind of works but just kind of.
I use it like this:
bool penetration = sat(c1, c2) && sat(c2, c1);
Where c1 and c2 are of type Convex, defined as:
class Convex
{
public:
float tx, ty;
public:
std::vector<Point> p;
void translate(float x, float y) {
tx = x;
ty = y;
}
};
(Point is a structure of float x, float y)
The points are typed in clockwise.
My current code (ignore Qt debug):
bool sat(Convex c1, Convex c2, QPainter *debug)
{
//Debug
QColor col[] = {QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255), QColor(0, 0, 0)};
bool ret = true;
int c1_faces = c1.p.size();
int c2_faces = c2.p.size();
//For every face in c1
for(int i = 0; i < c1_faces; i++)
{
//Grab a face (face x, face y)
float fx = c1.p[i].x - c1.p[(i + 1) % c1_faces].x;
float fy = c1.p[i].y - c1.p[(i + 1) % c1_faces].y;
//Create a perpendicular axis to project on (axis x, axis y)
float ax = -fy, ay = fx;
//Normalize the axis
float len_v = sqrt(ax * ax + ay * ay);
ax /= len_v;
ay /= len_v;
//Debug graphics (ignore)
debug->setPen(col[i]);
//Draw the face
debug->drawLine(QLineF(c1.tx + c1.p[i].x, c1.ty + c1.p[i].y, c1.p[(i + 1) % c1_faces].x + c1.tx, c1.p[(i + 1) % c1_faces].y + c1.ty));
//Draw the axis
debug->save();
debug->translate(c1.p[i].x, c1.p[i].y);
debug->drawLine(QLineF(c1.tx, c1.ty, ax * 100 + c1.tx, ay * 100 + c1.ty));
debug->drawEllipse(QPointF(ax * 100 + c1.tx, ay * 100 + c1.ty), 10, 10);
debug->restore();
//Carve out the min and max values
float c1_min = FLT_MAX, c1_max = FLT_MIN;
float c2_min = FLT_MAX, c2_max = FLT_MIN;
//Project every point in c1 on the axis and store min and max
for(int j = 0; j < c1_faces; j++)
{
float c1_proj = (ax * (c1.p[j].x + c1.tx) + ay * (c1.p[j].y + c1.ty)) / (ax * ax + ay * ay);
c1_min = min(c1_proj, c1_min);
c1_max = max(c1_proj, c1_max);
}
//Project every point in c2 on the axis and store min and max
for(int j = 0; j < c2_faces; j++)
{
float c2_proj = (ax * (c2.p[j].x + c2.tx) + ay * (c2.p[j].y + c2.ty)) / (ax * ax + ay * ay);
c2_min = min(c2_proj, c2_min);
c2_max = max(c2_proj, c2_max);
}
//Return if the projections do not overlap
if(!(c1_max >= c2_min && c1_min <= c2_max))
ret = false; //return false;
}
return ret; //return true;
}
What am i doing wrong? It registers collision perfectly but is over sensitive on one edge (in my test using a triangle and a diamond):
//Triangle
push_back(Point(0, -150));
push_back(Point(0, 50));
push_back(Point(-100, 100));
//Diamond
push_back(Point(0, -100));
push_back(Point(100, 0));
push_back(Point(0, 100));
push_back(Point(-100, 0));
I am getting this mega-adhd over this, please help me out :)
http://u8999827.fsdata.se/sat.png
OK, I was wrong the first time. Looking at your picture of a failure case it is obvious a separating axis exists and is one of the normals (the normal to the long edge of the triangle). The projection is correct, however, your bounds are not.
I think the error is here:
float c1_min = FLT_MAX, c1_max = FLT_MIN;
float c2_min = FLT_MAX, c2_max = FLT_MIN;
FLT_MIN is the smallest normal positive number representable by a float, not the most negative number. In fact you need:
float c1_min = FLT_MAX, c1_max = -FLT_MAX;
float c2_min = FLT_MAX, c2_max = -FLT_MAX;
or even better for C++
float c1_min = std::numeric_limits<float>::max(), c1_max = -c1_min;
float c2_min = std::numeric_limits<float>::max(), c2_max = -c2_min;
because you're probably seeing negative projections onto the axis.