This is going to be a long one. I am still very new to coding, started 3 months ago so I know my code is not perfect, any criticism beyond the question is more than welcome. I have specifically avoided using pointers because I do not fully understand them, I can use them but I dont trust that I will use them correctly in a program like this.
First things first, I have a version of this where there is only 1 hidden layer and the net works perfectly. I have started running into problems since I tried to expand the number of hidden layers.
Some info on the net:
-I am using softmax output activation as I have 3 output neurons.
-I am using tanh as my activation function on the rest of the net.
-The file being read for the input has a format of
"input: 0.56 0.76 0.23 0.67"
"output: 0.0 0.0 1.0" (this is the target)
-The weights for connecting layer 1 neuron to layer 2 neuron are stored in layer 1 one neuron.
-The bias's for each neuron are stored in that neuron.
-The target is 1.0 0.0 0.0 if the sum of the input numbers is below one, 0.0 1.0 0.0 if sum is between 1 and 2, 0.0 0.0 1.0 if sum is above 2.
-using L1 regularization.
Those problems specifically being:
The softmax output values do not move from an relatively equalised range ie:
(position 1 and 2 in the target vector have a roughly 50/50 occurance rate while position 3 less than 3% occurance rate. so by relatively equalised I mean the softmax output generally looks something like
"0.56.... 0.48.... 0.02..." even after 500 epochs.
The weights at the hidden layer closer to inputlayer dont change much at all, which is what i think vanishing gradients are. I might be wrong on this. But the weights at hiddenlayer closest to output are ending up at between -50 & 50 (which i think is okay?)
Things I have tried:
I have tried using Relu, parametric Relu, exponential Relu, but with all of these the softmax output value for neuron 3 keeps rising, the other 2 neurons values keep falling. these values continue their trajectory until either 500 epochs have been reached or they just turn into nans. (I think this is to do with the structure of my code rather than the Relu function itself).
If I set the number of hidden layers above 3 while using relu, it immediately spits out nans, within the first epoch.
The backprop function is pretty long, but this is specifically because I have deconstructed it many times over to try and figure out where I might be mismatching values or something. I do have it in a condensed version but I feel I have a higher chance of being completely off the mark there than I do if I have it deconstructed.
I have included the Relu function code that I used, it is the first time I use it so I might be wrong on that aswell but I dont think so, I have double checked multiple times. The Relu in the code is specifically "Elu" or exponential relu.
here is the code for the net:
#include <iostream>
#include <fstream>
#include <cmath>
#include <vector>
#include <sstream>
#include <random>
#include <string>
#include <iomanip>
double randomt(double x, double y)
{
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_real_distribution<double> dist(x, y);
return dist(mt);
}
class InputN
{
public:
double val{};
std::vector <double> weights{};
};
class HiddenN
{
public:
double preactval{};
double actval{};
double actvalPD{};
double preactvalpd{};
std::vector <double> weights{};
double bias{};
};
class OutputN
{
public:
double preactval{};
double actval{};
double preactvalpd{};
double bias{};
};
class Net
{
public:
std::vector <InputN> inneurons{};
std::vector <std::vector <HiddenN>> hiddenneurons{};
std::vector <OutputN> outputneurons{};
double lambda{ 0.015 };
double alpha{ 0.02 };
};
double tanhderiv(double val)
{
return 1 - tanh(val) * tanh(val);
}
double Relu(double val)
{
if (val < 0) return 0.01 *(exp(val) - 1);
else return val;
}
double Reluderiv(double val)
{
if (val < 0) return Relu(val) + 0.01;
else return 1;
}
double regularizer(double weight)
{
double absval{};
if (weight < 0) absval = weight - weight - weight;
else if (weight > 0 || weight == 0) absval = weight;
else;
if (absval > 0) return 1;
else if (absval < 0) return -1;
else if (absval == 0) return 0;
else return 2;
}
void feedforward(Net& net)
{
double sum{};
int prevlayer{};
for (size_t Hsize = 0; Hsize < net.hiddenneurons.size(); Hsize++)
{
//std::cout << "in first loop" << '\n';
prevlayer = Hsize - 1;
for (size_t Hel = 0; Hel < net.hiddenneurons[Hsize].size(); Hel++)
{
//std::cout << "in second loop" << '\n';
if (Hsize == 0)
{
//std::cout << "in first if" << '\n';
for (size_t Isize = 0; Isize < net.inneurons.size(); Isize++)
{
//std::cout << "in fourth loop" << '\n';
sum += (net.inneurons[Isize].val * net.inneurons[Isize].weights[Hel]);
}
net.hiddenneurons[Hsize][Hel].preactval = net.hiddenneurons[Hsize][Hel].bias + sum;
net.hiddenneurons[Hsize][Hel].actval = tanh(sum);
sum = 0;
//std::cout << "first if done" << '\n';
}
else
{
//std::cout << "in else" << '\n';
for (size_t prs = 0; prs < net.hiddenneurons[prevlayer].size(); prs++)
{
//std::cout << "in fourth loop" << '\n';
sum += net.hiddenneurons[prevlayer][prs].actval * net.hiddenneurons[prevlayer][prs].weights[Hel];
}
//std::cout << "fourth loop done" << '\n';
net.hiddenneurons[Hsize][Hel].preactval = net.hiddenneurons[Hsize][Hel].bias + sum;
net.hiddenneurons[Hsize][Hel].actval = tanh(sum);
//std::cout << "else done" << '\n';
sum = 0;
}
}
}
//std::cout << "first loop done " << '\n';
int lasthid = net.hiddenneurons.size() - 1;
for (size_t Osize = 0; Osize < net.outputneurons.size(); Osize++)
{
for (size_t Hsize = 0; Hsize < net.hiddenneurons[lasthid].size(); Hsize++)
{
sum += (net.hiddenneurons[lasthid][Hsize].actval * net.hiddenneurons[lasthid][Hsize].weights[Osize]);
}
net.outputneurons[Osize].preactval = net.outputneurons[Osize].bias + sum;
}
}
void softmax(Net& net)
{
double sum{};
for (size_t Osize = 0; Osize < net.outputneurons.size(); Osize++)
{
sum += exp(net.outputneurons[Osize].preactval);
}
for (size_t Osize = 0; Osize < net.outputneurons.size(); Osize++)
{
net.outputneurons[Osize].actval = exp(net.outputneurons[Osize].preactval) / sum;
}
}
void lossfunc(Net& net, std::vector <double> target)
{
int pos{ -1 };
double val{};
for (size_t t = 0; t < target.size(); t++)
{
pos += 1;
if (target[t] > 0)
{
break;
}
}
for (size_t s = 0; net.outputneurons.size(); s++)
{
val = -log(net.outputneurons[pos].actval);
}
}
void backprop(Net& net, std::vector<double>& target)
{
for (size_t outI = 0; outI < net.outputneurons.size(); outI++)
{
double PD = target[outI] - net.outputneurons[outI].actval;
net.outputneurons[outI].preactvalpd = PD * -1;
}
size_t lasthid = net.hiddenneurons.size() - 1;
for (size_t LH = 0; LH < net.hiddenneurons[lasthid].size(); LH++)
{
for (size_t LHW = 0; LHW < net.hiddenneurons[lasthid][LH].weights.size(); LHW++)
{
double weight = net.hiddenneurons[lasthid][LH].weights[LHW];
double PD = net.outputneurons[LHW].preactvalpd * net.hiddenneurons[lasthid][LH].actval;
PD = PD * -1;
double delta = PD - (net.lambda * regularizer(weight));
weight = weight + (net.alpha * delta);
net.hiddenneurons[lasthid][LH].weights[LHW] = weight;
}
}
for (size_t OB = 0; OB < net.outputneurons.size(); OB++)
{
double bias = net.outputneurons[OB].bias;
double BPD = net.outputneurons[OB].preactvalpd;
BPD = BPD * -1;
double Delta = BPD;
bias = bias + (net.alpha * Delta);
}
for (size_t HPD = 0; HPD < net.hiddenneurons[lasthid].size(); HPD++)
{
double PD{};
for (size_t HW = 0; HW < net.outputneurons.size(); HW++)
{
PD += net.hiddenneurons[lasthid][HPD].weights[HW] * net.outputneurons[HW].preactvalpd;
}
net.hiddenneurons[lasthid][HPD].actvalPD = PD;
PD = 0;
}
for (size_t HPD = 0; HPD < net.hiddenneurons[lasthid].size(); HPD++)
{
net.hiddenneurons[lasthid][HPD].preactvalpd = net.hiddenneurons[lasthid][HPD].actvalPD * tanhderiv(net.hiddenneurons[lasthid][HPD].preactval);
}
for (size_t AllHid = net.hiddenneurons.size() - 2; AllHid > -1; AllHid--)
{
size_t uplayer = AllHid + 1;
for (size_t cl = 0; cl < net.hiddenneurons[AllHid].size(); cl++)
{
for (size_t clw = 0; clw < net.hiddenneurons[AllHid][cl].weights.size(); clw++)
{
double weight = net.hiddenneurons[AllHid][cl].weights[clw];
double PD = net.hiddenneurons[uplayer][clw].preactvalpd * net.hiddenneurons[AllHid][cl].actval;
PD = PD * -1;
double delta = PD - (net.lambda * regularizer(weight));
weight = weight + (net.alpha * delta);
net.hiddenneurons[AllHid][cl].weights[clw] = weight;
}
}
for (size_t up = 0; up < net.hiddenneurons[uplayer].size(); up++)
{
double bias = net.hiddenneurons[uplayer][up].bias;
double PD = net.hiddenneurons[uplayer][up].preactvalpd;
PD = PD * -1;
double delta = PD;
bias = bias + (net.alpha * delta);
}
for (size_t APD = 0; APD < net.hiddenneurons[AllHid].size(); APD++)
{
double PD{};
for (size_t APDW = 0; APDW < net.hiddenneurons[AllHid][APD].weights.size(); APDW++)
{
PD += net.hiddenneurons[AllHid][APD].weights[APDW] * net.hiddenneurons[uplayer][APDW].preactvalpd;
}
net.hiddenneurons[AllHid][APD].actvalPD = PD;
PD = 0;
}
for (size_t PPD = 0; PPD < net.hiddenneurons[AllHid].size(); PPD++)
{
double PD = net.hiddenneurons[AllHid][PPD].actvalPD * tanhderiv(net.hiddenneurons[AllHid][PPD].preactval);
net.hiddenneurons[AllHid][PPD].preactvalpd = PD;
}
}
for (size_t IN = 0; IN < net.inneurons.size(); IN++)
{
for (size_t INW = 0; INW < net.inneurons[IN].weights.size(); INW++)
{
double weight = net.inneurons[IN].weights[INW];
double PD = net.hiddenneurons[0][INW].preactvalpd * net.inneurons[IN].val;
PD = PD * -1;
double delta = PD - (net.lambda * regularizer(weight));
weight = weight + (net.alpha * delta);
net.inneurons[IN].weights[INW] = weight;
}
}
for (size_t hidB = 0; hidB < net.hiddenneurons[0].size(); hidB++)
{
double bias = net.hiddenneurons[0][hidB].bias;
double PD = net.hiddenneurons[0][hidB].preactvalpd;
PD = PD * -1;
double delta = PD;
bias = bias + (net.alpha * delta);
net.hiddenneurons[0][hidB].bias = bias;
}
}
int main()
{
std::vector <double> invals{ };
std::vector <double> target{ };
Net net;
InputN Ineuron;
HiddenN Hneuron;
OutputN Oneuron;
int IN = 4;
int HIDLAYERS = 4;
int HID = 8;
int OUT = 3;
for (int i = 0; i < IN; i++)
{
net.inneurons.push_back(Ineuron);
for (int m = 0; m < HID; m++)
{
net.inneurons.back().weights.push_back(randomt(0.0, 0.5));
}
}
//std::cout << "first loop done" << '\n';
for (int s = 0; s < HIDLAYERS; s++)
{
net.hiddenneurons.push_back(std::vector <HiddenN>());
if (s == HIDLAYERS - 1)
{
for (int i = 0; i < HID; i++)
{
net.hiddenneurons[s].push_back(Hneuron);
for (int m = 0; m < OUT; m++)
{
net.hiddenneurons[s].back().weights.push_back(randomt(0.0, 0.5));
}
net.hiddenneurons[s].back().bias = 1.0;
}
}
else
{
for (int i = 0; i < HID; i++)
{
net.hiddenneurons[s].push_back(Hneuron);
for (int m = 0; m < HID; m++)
{
net.hiddenneurons[s].back().weights.push_back(randomt(0.0, 0.5));
}
net.hiddenneurons[s].back().bias = 1.0;
}
}
}
//std::cout << "second loop done" << '\n';
for (int i = 0; i < OUT; i++)
{
net.outputneurons.push_back(Oneuron);
net.outputneurons.back().bias = randomt(0.0, 0.5);
}
//std::cout << "third loop done" << '\n';
int count{};
std::ifstream fileread("N.txt");
for (int epoch = 0; epoch < 500; epoch++)
{
count = 0;
if (epoch == 100 || epoch == 100 * 2 || epoch == 100 * 3 || epoch == 100 * 4 || epoch == 499)
{
printvals("no", net);
}
fileread.clear(); fileread.seekg(0, std::ios::beg);
while (fileread.is_open())
{
std::cout << '\n' << "epoch: " << epoch << '\n';
std::string fileline{};
fileread >> fileline;
if (fileline == "in:")
{
std::string input{};
double nums{};
std::getline(fileread, input);
std::stringstream ss(input);
while (ss >> nums)
{
invals.push_back(nums);
}
}
if (fileline == "out:")
{
std::string output{};
double num{};
std::getline(fileread, output);
std::stringstream ss(output);
while (ss >> num)
{
target.push_back(num);
}
}
count += 1;
if (count == 2)
{
for (size_t inv = 0; inv < invals.size(); inv++)
{
net.inneurons[inv].val = invals[inv];
}
//std::cout << "calling feedforward" << '\n';
feedforward(net);
//std::cout << "ff done" << '\n';
softmax(net);
printvals("output", net);
std::cout << "target: " << '\n';
for (auto element : target) std::cout << element << " / ";
std::cout << '\n';
backprop(net, target);
invals.clear();
target.clear();
count = 0;
}
if (fileread.eof()) break;
}
}
//std::cout << "fourth loop done" << '\n';
return 1;
}
Much aprecciated to anyone who actually made it through all that! :)
I have to implement a DCT algorithm in C++, here is my present code :
// dct: computes the discrete cosinus tranform of a 8x8 block
template<typename Tin=uchar,typename Tout=float>
inline cv::Mat_<Tout> dct(const cv::Mat_<Tin>& oBlock) {
int indexNumber;
float pi = 3.14159265359;
float fcoscos, fxy, cos1, cos2, forCos1, forCos2;
cv::Mat_<Tout> resultBloc(8, 8);
for (int u = 0; u < oBlock.rows; u++){
for (int v = 0; v < oBlock.cols; v++){
float cu=0, cv=0, Result=0;
// calcul c(u)
if (u == 0){
cu = (float)sqrt((float)1 / (float)oBlock.rows);
}
else {
cu = (float)sqrt((float)2 / (float)oBlock.rows);
}
// calcul c(v)
if (v == 0){
cv = (float)sqrt((float)1 / (float)oBlock.cols);
}
else {
cv = (float)sqrt((float)2 / (float)oBlock.cols);
}
float sums = 0;
for (int x = 0; x < oBlock.rows; x++){
for (int y = 0; y < oBlock.cols; y++){
indexNumber = x * oBlock.rows + y;
fxy = (int)oBlock.data[indexNumber];
forCos1 = (pi*((2 * x) + 1)*u) / (2 * oBlock.rows);
forCos2 = (pi*((2 * y) + 1)*v) / (2 * oBlock.cols);
cos1 = cos(forCos1);
cos2 = cos(forCos2);
fcoscos = fxy * cos1 * cos2;
sums += fcoscos;
}
}
// calcul total
Result = sums*cu*cv;
indexNumber = u * oBlock.rows + v;
resultBloc.data[indexNumber] = Result;
}
}
return resultBloc;
}
I compared the result with the cv DCT algorithm as follow :
cv::Mat_<float> tempImage(8,8);
for (int i = 0; i < vecImageCut[0].cols*vecImageCut[0].rows; i++){
tempImage.data[i] = (int)vecImageCut[0].data[i];
}
cv::Mat_<float> dctCV;
cv::dct(tempImage, dctCV);
for (int i = 0; i < blocksAfterDCT[0].cols*blocksAfterDCT[0].rows; i++){
std::cerr << "Difference DCT for pixel " << i << " : " << dctCV.data[i] - blocksAfterDCT[0].data[i] << std::endl;
}
The results between my DCT and the cv DCT are very different so i assume my DCT algorithm is wrong but i searched for hours and i can't find my mistake, can anyone tell me where i did something wrong ?
Your index calculations are wrong. In indexNumber = x * oBlock.rows + y;, since x is counting rows it needs to be multiplied by the number of columns:
indexNumber = x * oBlock.cols + y;
The same for indexNumber = u * oBlock.rows + v;
indexNumber = u * oBlock.cols + v;
I have reference paths that are defined by a series of points. Given a starting point and additional points in a given direction, I need to effectively split the path created by the points into two distinct paths (directional) in a manner that allows me to draw and work with the two paths independently. The following screenshot shows 8 path examples. The "starting point" is the one with the white circle on it. If you orient yourself from the starting point towards the next point, then the path suggested by the red circles always should be to the "right" of the path defined by the given points.
In the screen, paths 2, 3, 5, 6, and 7 are correct. In paths 1, 4, and 8, essentially when the path starts on the right and moves left, then the position of the parallel path points are correct, but they are swapped in some instance (green where red should be, etc).
I'm somehow misusing atan2() (I think) to get the proper angle and or calculate the positions. For the first and last points, I'm calculating the angle to the adjacent point and the drawing the red and green path points at 90 degree offsets from that angle. For the points in the middle of the path, I'm looking at the angle to the previous point and the angle to the next point and placing the points in a manner that bisects the angles.
How do I properly calculate these angles to get the red and green parallel path points on the proper side of the reference line?
The problem likely is in TestLine::calculateParallelPoints().
I used openFrameworks 0.90 for this. Here's the code:
ofApp.h
#pragma once
#include "ofMain.h"
#include "TestLine.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
int sectors_wide;
int sectors_tall;
vector<TestLine> testLines;
};
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
ofSetBackgroundColorHex(0x000000);
sectors_wide = 4;
sectors_tall = 2;
TestLine t1 = TestLine(0,0,sectors_wide,sectors_tall);
t1.raw_points.push_back(ofPoint(0.9,0.5));
t1.raw_points.push_back(ofPoint(0.8,0.6));
t1.raw_points.push_back(ofPoint(0.7,0.4));
t1.raw_points.push_back(ofPoint(0.6,0.6));
t1.raw_points.push_back(ofPoint(0.5,0.4));
t1.raw_points.push_back(ofPoint(0.4,0.4));
t1.raw_points.push_back(ofPoint(0.3,0.5));
testLines.push_back(t1);
TestLine t2 = TestLine(1,0,sectors_wide,sectors_tall);
t2.raw_points.push_back(ofPoint(0.3,0.5)); //
t2.raw_points.push_back(ofPoint(0.4,0.4));
t2.raw_points.push_back(ofPoint(0.5,0.4));
t2.raw_points.push_back(ofPoint(0.6,0.6));
t2.raw_points.push_back(ofPoint(0.7,0.4));
t2.raw_points.push_back(ofPoint(0.8,0.6));
t2.raw_points.push_back(ofPoint(0.9,0.5));
testLines.push_back(t2);
TestLine t3 = TestLine(2,0,sectors_wide,sectors_tall);
t3.raw_points.push_back(ofPoint(0.1,0.2));
t3.raw_points.push_back(ofPoint(0.7,0.4));
t3.raw_points.push_back(ofPoint(0.4,0.45));
t3.raw_points.push_back(ofPoint(0.6,0.5));
t3.raw_points.push_back(ofPoint(0.9,0.9));
testLines.push_back(t3);
TestLine t4 = TestLine(3,0,sectors_wide,sectors_tall);
t4.raw_points.push_back(ofPoint(0.5,0.5));
t4.raw_points.push_back(ofPoint(0.9,0.5));
t4.raw_points.push_back(ofPoint(0.5,0.1));
t4.raw_points.push_back(ofPoint(0.1,0.1));
t4.raw_points.push_back(ofPoint(0.1,0.8));
t4.raw_points.push_back(ofPoint(0.8,0.6));
testLines.push_back(t4);
TestLine t5 = TestLine(0,1,sectors_wide,sectors_tall);
t5.raw_points.push_back(ofPoint(0.4,0.4));
t5.raw_points.push_back(ofPoint(0.6,0.5));
t5.raw_points.push_back(ofPoint(0.8,0.4));
testLines.push_back(t5);
TestLine t6 = TestLine(1,1,sectors_wide,sectors_tall);
t6.raw_points.push_back(ofPoint(0.7,0.1));
t6.raw_points.push_back(ofPoint(0.2,0.3));
t6.raw_points.push_back(ofPoint(0.7,0.5));
testLines.push_back(t6);
TestLine t7 = TestLine(2,1,sectors_wide,sectors_tall);
t7.raw_points.push_back(ofPoint(0.2,0.1));
t7.raw_points.push_back(ofPoint(0.7,0.3));
t7.raw_points.push_back(ofPoint(0.2,0.5));
testLines.push_back(t7);
TestLine t8 = TestLine(3,1,sectors_wide,sectors_tall);
t8.raw_points.push_back(ofPoint(0.8,0.5));
t8.raw_points.push_back(ofPoint(0.6,0.4));
t8.raw_points.push_back(ofPoint(0.4,0.5));
testLines.push_back(t8);
// Convert raw points to real points in the grid space
for (int i = 0; i < testLines.size(); i++) {
testLines[i].processRawPoints();
testLines[i].calculateParallelPoints();
}
}
//--------------------------------------------------------------
void ofApp::update(){
}
//--------------------------------------------------------------
void ofApp::draw(){
ofSetBackgroundColorHex(0x000000);
ofSetColor(255, 255, 255);
for (int i = 0; i < testLines.size(); i++) {
testLines[i].displayTestLine();
}
}
TestLine.h
#include "ofMain.h"
class TestLine {
public:
TestLine(float _x, float _y, int _sec_wide, int _sec_tall);
void displayTestLine();
void calculateParallelPoints();
void processRawPoints();
float x_coord;
float y_coord;
float x_min;
float x_max;
float y_min;
float y_max;
vector<float> angles;
vector<ofPoint> raw_points;
vector<ofPoint> points;
vector<ofPoint> forward_points;
vector<ofPoint> reverse_points;
ofPolyline line;
ofPolyline forward_line;
ofPolyline reverse_line;
};
TestLine.cpp
#include "TestLine.h"
TestLine::TestLine(float _x, float _y, int _sec_wide, int _sec_tall){
x_coord = _x;
y_coord = _y;
int w = ofGetWindowWidth();
int h = ofGetWindowHeight();
float allowed_w = (float)w / _sec_wide;
float allowed_h = (float)h / _sec_tall;
x_min = x_coord * allowed_w;
x_max = x_min + allowed_w;
y_min = y_coord * allowed_h;
y_max = y_min + allowed_h;
}
void TestLine::calculateParallelPoints(){
for (int i = 0; i < points.size(); i++) {
if (i == 0) {
ofVec2f v2 = points[i];
ofVec2f v1 = points[i+1];
float angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
angles.push_back(angle);
cout << "Start: " << angle << endl;
}
if (i > 0 && i < points.size() - 1) {
ofVec2f v1 = points[i];
ofVec2f v2 = points[i-1];
float back_angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
v2 = points[i];
v1 = points[i+1];
float front_angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
float final_angle = (back_angle + front_angle) / 2;
cout << "BACK ANGLE: " << back_angle << ", FRONT ANGLE: " << front_angle << ", FINAL ANGLE: " << final_angle << endl;
float prev_x = points[i-1].x;
float prev_y = points[i-1].y;
float this_x = points[i].x;
float this_y = points[i].y;
float next_x = points[i+1].x;
float next_y = points[i+1].y;
angles.push_back(final_angle);
}
if (i == points.size() - 1) {
ofVec2f v1 = points[i];
ofVec2f v2 = points[i-1];
float angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
angles.push_back(angle);
cout << "End: " << angle << endl;
}
line.addVertex(points[i]);
}
// Now using the points and the angles to calculate the forward and reverse points
for (int i = 0; i < points.size(); i++) {
float forward_angle = angles[i] + 90;
float reverse_angle = angles[i] - 90;
// cout << forward_angle << ", " << reverse_angle << endl;
float forward_x = points[i].x + cos(ofDegToRad(forward_angle)) * 8;
float forward_y = points[i].y + sin(ofDegToRad(forward_angle)) * 8;
forward_points.push_back(ofPoint(forward_x, forward_y));
float reverse_x = points[i].x + cos(ofDegToRad(reverse_angle)) * 8;
float reverse_y = points[i].y + sin(ofDegToRad(reverse_angle)) * 8;
reverse_points.push_back(ofPoint(reverse_x, reverse_y));
}
}
void TestLine::processRawPoints(){
for (int i = 0; i < raw_points.size(); i++) {
float newx = ofMap(raw_points[i].x, 0, 1, x_min, x_max);
float newy = ofMap(raw_points[i].y, 0, 1, y_min, y_max);
points.push_back(ofPoint(newx,newy));
}
}
void TestLine::displayTestLine(){
ofSetColor(128,128,128);
line.draw();
ofSetColor(255, 255, 255);
ofDrawCircle(points[0].x, points[0].y, 3);
ofSetColor(255, 0, 0);
for (int i = 0; i < forward_points.size(); i++) {
ofDrawCircle(forward_points[i].x, forward_points[i].y, 2);
}
ofSetColor(0, 255, 0);
for (int i = 0; i < reverse_points.size(); i++) {
ofDrawCircle(reverse_points[i].x, reverse_points[i].y, 2);
}
}
I ended up handling this with several conditional statements, though I feel there's probably a better way to handle it with trig. Here's the amended TestLine.cpp file.
TestLine.cpp
#include "TestLine.h"
TestLine::TestLine(float _x, float _y, int _sec_wide, int _sec_tall){
x_coord = _x;
y_coord = _y;
int w = ofGetWindowWidth();
int h = ofGetWindowHeight();
float allowed_w = (float)w / _sec_wide;
float allowed_h = (float)h / _sec_tall;
x_min = x_coord * allowed_w;
x_max = x_min + allowed_w;
y_min = y_coord * allowed_h;
y_max = y_min + allowed_h;
}
void TestLine::calculateParallelPoints(){
for (int i = 0; i < points.size(); i++) {
if (i == 0) {
ofVec2f v2 = points[i];
ofVec2f v1 = points[i+1];
float angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
angles.push_back(angle);
cout << endl;
cout << "Start: " << angle << endl;
}
if (i > 0 && i < points.size() - 1) {
ofVec2f v1 = points[i];
ofVec2f v2 = points[i-1];
float back_angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
v2 = points[i];
v1 = points[i+1];
float front_angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
float final_angle = (back_angle + front_angle) / 2; // back_angle + front_angle
float prev_x = points[i-1].x;
float prev_y = points[i-1].y;
float this_x = points[i].x;
float this_y = points[i].y;
float next_x = points[i+1].x;
float next_y = points[i+1].y;
// Here is the addition that addressed the problem.
if ((prev_x > this_x && prev_y <= this_y && next_x < this_x && next_y < this_y) ||
(prev_x > this_x && prev_y > this_y && next_x < this_x && next_y >= this_y)) {
final_angle += 180;
}
cout << "BACK ANGLE: " << back_angle << ", FRONT ANGLE: " << front_angle << ", FINAL ANGLE: " << final_angle << endl;
angles.push_back(final_angle);
}
if (i == points.size() - 1) {
ofVec2f v1 = points[i];
ofVec2f v2 = points[i-1];
float angle = ofRadToDeg(atan2(v1.y - v2.y, v1.x - v2.x));
angles.push_back(angle);
cout << "End: " << angle << endl << endl;
}
line.addVertex(points[i]);
}
// Now using the points and the angles to calculate the forward and reverse points
for (int i = 0; i < points.size(); i++) {
float forward_angle = angles[i] + 90;
float reverse_angle = angles[i] - 90;
// cout << forward_angle << ", " << reverse_angle << endl;
float forward_x = points[i].x + cos(ofDegToRad(forward_angle)) * 8;
float forward_y = points[i].y + sin(ofDegToRad(forward_angle)) * 8;
forward_points.push_back(ofPoint(forward_x, forward_y));
float reverse_x = points[i].x + cos(ofDegToRad(reverse_angle)) * 8;
float reverse_y = points[i].y + sin(ofDegToRad(reverse_angle)) * 8;
reverse_points.push_back(ofPoint(reverse_x, reverse_y));
}
}
void TestLine::processRawPoints(){
for (int i = 0; i < raw_points.size(); i++) {
float newx = ofMap(raw_points[i].x, 0, 1, x_min, x_max);
float newy = ofMap(raw_points[i].y, 0, 1, y_min, y_max);
points.push_back(ofPoint(newx,newy));
}
}
void TestLine::displayTestLine(){
ofSetColor(128,128,128);
line.draw();
ofSetColor(255, 255, 255);
ofDrawCircle(points[0].x, points[0].y, 3);
ofSetColor(255, 0, 0);
for (int i = 0; i < forward_points.size(); i++) {
ofDrawCircle(forward_points[i].x, forward_points[i].y, 2);
}
ofSetColor(0, 255, 0);
for (int i = 0; i < reverse_points.size(); i++) {
ofDrawCircle(reverse_points[i].x, reverse_points[i].y, 2);
}
}
I know stackoverflow has already some questions and answers about this topic but I cannot deal with them for my specific Problem.
Here is my Code:
int main()
{
std::vector <int> v;
double x_screen, y_screen;
double screen_width = 640.0;
double screen_height = 480.0;
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
double half_screen_width = screen_width / 2;
double half_screen_height = screen_height / 2;
double window_aspect = screen_width / screen_height;
double x_3D, y_3D, z_3D;
for (int i = 0; i < 3; i++){
v.push_back(i);
++v[i];
}
std::cout << "3D Point:" << std::endl;
for (int j = 0; j < v.size(); j++)
std::cout << v[j] << std::endl;
x_3D = v[0] - viewport[0];
y_3D = v[1] - viewport[1];
z_3D = v[2] - viewport[2];
x_screen = (+(x_3D / z_3D)+half_screen_width)*half_screen_width;
y_screen = (-(y_3D / z_3D)+half_screen_height)*half_screen_height;
if (window_aspect > 1.0)
x_screen = x_screen / window_aspect;
else
y_screen = y_screen*window_aspect;
std::cout << "2D Point :" << std::endl;
std::cout << "[" << x_screen << "," << y_screen << "]" << std::endl;
getchar();
getchar();
return 0;
}
Output:
3D Point : [1,2,3]
2D Point : [77040,57360]
The Tutorial where i got the mathematic Background is here :
http://www.flipcode.com/archives/Plotting_A_3D_Point_On_A_2D_Screen.shtml
Can anyone tell me if this result is logical? I am new on this topic and cannot interpret the result.
I am writing a bit of code to animate a point using a sequence of positions. In order to have a decent result, I'd like to add some spline interpolation
to smoothen the transitions between positions. All the positions are separated by the same amount of time (let's say 500ms).
int delay = 500;
vector<Point> positions={ (0, 0) , (50, 20), (150, 100), (30, 120) };
Here is what i have done to make a linear interpolation (which seems to work properly), juste to give you an idea of what I'm looking for later on :
Point getPositionAt(int currentTime){
Point before, after, result;
int currentIndex = (currentTime / delay) % positions.size();
before = positions[currentIndex];
after = positions[(currentIndex + 1) % positions.size()];
// progress between [before] and [after]
double progress = fmod((((double)currentTime) / (double)delay), (double)positions.size()) - currentIndex;
result.x = before.x + (int)progress*(after.x - before.x);
result.y = before.y + (int)progress*(after.y - before.y);
return result;
}
So that was simple, but now what I would like to do is spline interpolation. Thanks !
I had to write a Bezier spline creation routine for an "entity" that was following a path in a game I am working on. I created a base class to handle a "SplineInterface" and the created two derived classes, one based on the classic spline technique (e.g. Sedgewick/Algorithms) an a second one based on Bezier Splines.
Here is the code. It is a single header file, with a few includes (most should be obvious):
#ifndef __SplineCommon__
#define __SplineCommon__
#include "CommonSTL.h"
#include "CommonProject.h"
#include "MathUtilities.h"
/* A Spline base class. */
class SplineBase
{
private:
vector<Vec2> _points;
bool _elimColinearPoints;
protected:
protected:
/* OVERRIDE THESE FUNCTIONS */
virtual void ResetDerived() = 0;
enum
{
NOM_SIZE = 32,
};
public:
SplineBase()
{
_points.reserve(NOM_SIZE);
_elimColinearPoints = true;
}
const vector<Vec2>& GetPoints() { return _points; }
bool GetElimColinearPoints() { return _elimColinearPoints; }
void SetElimColinearPoints(bool elim) { _elimColinearPoints = elim; }
/* OVERRIDE THESE FUNCTIONS */
virtual Vec2 Eval(int seg, double t) = 0;
virtual bool ComputeSpline() = 0;
virtual void DumpDerived() {}
/* Clear out all the data.
*/
void Reset()
{
_points.clear();
ResetDerived();
}
void AddPoint(const Vec2& pt)
{
// If this new point is colinear with the two previous points,
// pop off the last point and add this one instead.
if(_elimColinearPoints && _points.size() > 2)
{
int N = _points.size()-1;
Vec2 p0 = _points[N-1] - _points[N-2];
Vec2 p1 = _points[N] - _points[N-1];
Vec2 p2 = pt - _points[N];
// We test for colinearity by comparing the slopes
// of the two lines. If the slopes are the same,
// we assume colinearity.
float32 delta = (p2.y-p1.y)*(p1.x-p0.x)-(p1.y-p0.y)*(p2.x-p1.x);
if(MathUtilities::IsNearZero(delta))
{
_points.pop_back();
}
}
_points.push_back(pt);
}
void Dump(int segments = 5)
{
assert(segments > 1);
cout << "Original Points (" << _points.size() << ")" << endl;
cout << "-----------------------------" << endl;
for(int idx = 0; idx < _points.size(); ++idx)
{
cout << "[" << idx << "]" << " " << _points[idx] << endl;
}
cout << "-----------------------------" << endl;
DumpDerived();
cout << "-----------------------------" << endl;
cout << "Evaluating Spline at " << segments << " points." << endl;
for(int idx = 0; idx < _points.size()-1; idx++)
{
cout << "---------- " << "From " << _points[idx] << " to " << _points[idx+1] << "." << endl;
for(int tIdx = 0; tIdx < segments+1; ++tIdx)
{
double t = tIdx*1.0/segments;
cout << "[" << tIdx << "]" << " ";
cout << "[" << t*100 << "%]" << " ";
cout << " --> " << Eval(idx,t);
cout << endl;
}
}
}
};
class ClassicSpline : public SplineBase
{
private:
/* The system of linear equations found by solving
* for the 3 order spline polynomial is given by:
* A*x = b. The "x" is represented by _xCol and the
* "b" is represented by _bCol in the code.
*
* The "A" is formulated with diagonal elements (_diagElems) and
* symmetric off-diagonal elements (_offDiagElemns). The
* general structure (for six points) looks like:
*
*
* | d1 u1 0 0 0 | | p1 | | w1 |
* | u1 d2 u2 0 0 | | p2 | | w2 |
* | 0 u2 d3 u3 0 | * | p3 | = | w3 |
* | 0 0 u3 d4 u4 | | p4 | | w4 |
* | 0 0 0 u4 d5 | | p5 | | w5 |
*
*
* The general derivation for this can be found
* in Robert Sedgewick's "Algorithms in C++".
*
*/
vector<double> _xCol;
vector<double> _bCol;
vector<double> _diagElems;
vector<double> _offDiagElems;
public:
ClassicSpline()
{
_xCol.reserve(NOM_SIZE);
_bCol.reserve(NOM_SIZE);
_diagElems.reserve(NOM_SIZE);
_offDiagElems.reserve(NOM_SIZE);
}
/* Evaluate the spline for the ith segment
* for parameter. The value of parameter t must
* be between 0 and 1.
*/
inline virtual Vec2 Eval(int seg, double t)
{
const vector<Vec2>& points = GetPoints();
assert(t >= 0);
assert(t <= 1.0);
assert(seg >= 0);
assert(seg < (points.size()-1));
const double ONE_OVER_SIX = 1.0/6.0;
double oneMinust = 1.0 - t;
double t3Minust = t*t*t-t;
double oneMinust3minust = oneMinust*oneMinust*oneMinust-oneMinust;
double deltaX = points[seg+1].x - points[seg].x;
double yValue = t * points[seg + 1].y +
oneMinust*points[seg].y +
ONE_OVER_SIX*deltaX*deltaX*(t3Minust*_xCol[seg+1] - oneMinust3minust*_xCol[seg]);
double xValue = t*(points[seg+1].x-points[seg].x) + points[seg].x;
return Vec2(xValue,yValue);
}
/* Clear out all the data.
*/
virtual void ResetDerived()
{
_diagElems.clear();
_bCol.clear();
_xCol.clear();
_offDiagElems.clear();
}
virtual bool ComputeSpline()
{
const vector<Vec2>& p = GetPoints();
_bCol.resize(p.size());
_xCol.resize(p.size());
_diagElems.resize(p.size());
for(int idx = 1; idx < p.size(); ++idx)
{
_diagElems[idx] = 2*(p[idx+1].x-p[idx-1].x);
}
for(int idx = 0; idx < p.size(); ++idx)
{
_offDiagElems[idx] = p[idx+1].x - p[idx].x;
}
for(int idx = 1; idx < p.size(); ++idx)
{
_bCol[idx] = 6.0*((p[idx+1].y-p[idx].y)/_offDiagElems[idx] -
(p[idx].y-p[idx-1].y)/_offDiagElems[idx-1]);
}
_xCol[0] = 0.0;
_xCol[p.size()-1] = 0.0;
for(int idx = 1; idx < p.size()-1; ++idx)
{
_bCol[idx+1] = _bCol[idx+1] - _bCol[idx]*_offDiagElems[idx]/_diagElems[idx];
_diagElems[idx+1] = _diagElems[idx+1] - _offDiagElems[idx]*_offDiagElems[idx]/_diagElems[idx];
}
for(int idx = (int)p.size()-2; idx > 0; --idx)
{
_xCol[idx] = (_bCol[idx] - _offDiagElems[idx]*_xCol[idx+1])/_diagElems[idx];
}
return true;
}
};
/* Bezier Spline Implementation
* Based on this article:
* http://www.particleincell.com/blog/2012/bezier-splines/
*/
class BezierSpine : public SplineBase
{
private:
vector<Vec2> _p1Points;
vector<Vec2> _p2Points;
public:
BezierSpine()
{
_p1Points.reserve(NOM_SIZE);
_p2Points.reserve(NOM_SIZE);
}
/* Evaluate the spline for the ith segment
* for parameter. The value of parameter t must
* be between 0 and 1.
*/
inline virtual Vec2 Eval(int seg, double t)
{
assert(seg < _p1Points.size());
assert(seg < _p2Points.size());
double omt = 1.0 - t;
Vec2 p0 = GetPoints()[seg];
Vec2 p1 = _p1Points[seg];
Vec2 p2 = _p2Points[seg];
Vec2 p3 = GetPoints()[seg+1];
double xVal = omt*omt*omt*p0.x + 3*omt*omt*t*p1.x +3*omt*t*t*p2.x+t*t*t*p3.x;
double yVal = omt*omt*omt*p0.y + 3*omt*omt*t*p1.y +3*omt*t*t*p2.y+t*t*t*p3.y;
return Vec2(xVal,yVal);
}
/* Clear out all the data.
*/
virtual void ResetDerived()
{
_p1Points.clear();
_p2Points.clear();
}
virtual bool ComputeSpline()
{
const vector<Vec2>& p = GetPoints();
int N = (int)p.size()-1;
_p1Points.resize(N);
_p2Points.resize(N);
if(N == 0)
return false;
if(N == 1)
{ // Only 2 points...just create a straight line.
// Constraint: 3*P1 = 2*P0 + P3
_p1Points[0] = (2.0/3.0*p[0] + 1.0/3.0*p[1]);
// Constraint: P2 = 2*P1 - P0
_p2Points[0] = 2.0*_p1Points[0] - p[0];
return true;
}
/*rhs vector*/
vector<Vec2> a(N);
vector<Vec2> b(N);
vector<Vec2> c(N);
vector<Vec2> r(N);
/*left most segment*/
a[0].x = 0;
b[0].x = 2;
c[0].x = 1;
r[0].x = p[0].x+2*p[1].x;
a[0].y = 0;
b[0].y = 2;
c[0].y = 1;
r[0].y = p[0].y+2*p[1].y;
/*internal segments*/
for (int i = 1; i < N - 1; i++)
{
a[i].x=1;
b[i].x=4;
c[i].x=1;
r[i].x = 4 * p[i].x + 2 * p[i+1].x;
a[i].y=1;
b[i].y=4;
c[i].y=1;
r[i].y = 4 * p[i].y + 2 * p[i+1].y;
}
/*right segment*/
a[N-1].x = 2;
b[N-1].x = 7;
c[N-1].x = 0;
r[N-1].x = 8*p[N-1].x+p[N].x;
a[N-1].y = 2;
b[N-1].y = 7;
c[N-1].y = 0;
r[N-1].y = 8*p[N-1].y+p[N].y;
/*solves Ax=b with the Thomas algorithm (from Wikipedia)*/
for (int i = 1; i < N; i++)
{
double m;
m = a[i].x/b[i-1].x;
b[i].x = b[i].x - m * c[i - 1].x;
r[i].x = r[i].x - m * r[i-1].x;
m = a[i].y/b[i-1].y;
b[i].y = b[i].y - m * c[i - 1].y;
r[i].y = r[i].y - m * r[i-1].y;
}
_p1Points[N-1].x = r[N-1].x/b[N-1].x;
_p1Points[N-1].y = r[N-1].y/b[N-1].y;
for (int i = N - 2; i >= 0; --i)
{
_p1Points[i].x = (r[i].x - c[i].x * _p1Points[i+1].x) / b[i].x;
_p1Points[i].y = (r[i].y - c[i].y * _p1Points[i+1].y) / b[i].y;
}
/*we have p1, now compute p2*/
for (int i=0;i<N-1;i++)
{
_p2Points[i].x=2*p[i+1].x-_p1Points[i+1].x;
_p2Points[i].y=2*p[i+1].y-_p1Points[i+1].y;
}
_p2Points[N-1].x = 0.5 * (p[N].x+_p1Points[N-1].x);
_p2Points[N-1].y = 0.5 * (p[N].y+_p1Points[N-1].y);
return true;
}
virtual void DumpDerived()
{
cout << " Control Points " << endl;
for(int idx = 0; idx < _p1Points.size(); idx++)
{
cout << "[" << idx << "] ";
cout << "P1: " << _p1Points[idx];
cout << " ";
cout << "P2: " << _p2Points[idx];
cout << endl;
}
}
};
#endif /* defined(__SplineCommon__) */
Some Notes
The classic spline will crash if you give it a vertical set of
points. That is why I created the Bezier...I have lots of vertical
lines/paths to follow.
The base class has an option to remove colinear points as you add
them. This uses a simple slope comparison of two lines to figure out
if they are on the same line. You don't have to do this, but for
long paths that are straight lines, it cuts down on cycles. When you
do a lot of pathfinding on a regular-spaced graph, you tend to get a
lot of continuous segments.
Here is an example of using the Bezier Spline:
/* Smooth the points on the path so that turns look
* more natural. We'll only smooth the first few
* points. Most of the time, the full path will not
* be executed anyway...why waste cycles.
*/
void SmoothPath(vector<Vec2>& path, int32 divisions)
{
const int SMOOTH_POINTS = 6;
BezierSpine spline;
if(path.size() < 2)
return;
// Cache off the first point. If the first point is removed,
// the we occasionally run into problems if the collision detection
// says the first node is occupied but the splined point is too
// close, so the FSM "spins" trying to find a sensor cell that is
// not occupied.
// Vec2 firstPoint = path.back();
// path.pop_back();
// Grab the points.
for(int idx = 0; idx < SMOOTH_POINTS && path.size() > 0; idx++)
{
spline.AddPoint(path.back());
path.pop_back();
}
// Smooth them.
spline.ComputeSpline();
// Push them back in.
for(int idx = spline.GetPoints().size()-2; idx >= 0; --idx)
{
for(int division = divisions-1; division >= 0; --division)
{
double t = division*1.0/divisions;
path.push_back(spline.Eval(idx, t));
}
}
// Push back in the original first point.
// path.push_back(firstPoint);
}
Notes
While the whole path could be smoothed, in this application, since
the path was changing every so often, it was better to just smooth
the first points and then connect it up.
The points are loaded in "reverse" order into the path vector. This
may or may not save cycles (I've slept since then).
This code is part of a much larger code base, but you can download it all on github and see a blog entry about it here.
You can look at this in action in this video.
Was this helpful?