仮想的に上から降ってきたボールを、骨格で跳ね返して下に落ちないようにするゲームを作ってみましょう。 説明を簡単にするため点数の処理などはしていません。
プロジェクトのソースを置くフォルダに Ball.h をコピーしてから、プロジェクトに加えます。
Ball.h |
#pragma once #include <iostream> #include <sstream> #include <opencv2/opencv.hpp> using namespace std; class Ball { #define EPS 1.0e-6 private: cv::Vec3f pos; cv::Vec3f vel; cv::Vec3f accel; double r; public: Ball() : pos(0, 0, 0), vel(0, 0, 0), accel(0, 0, 0), r(1.0) {} Ball(cv::Vec3f &pos, cv::Vec3f &v, cv::Vec3f &accel, double r) { this->pos = pos; this->vel = v; this->accel = accel; this->r = r; } ~Ball() {} void setPos(cv::Vec3f &pos) { this->pos = pos; } cv::Vec3f getPos() { return pos; } void setV(cv::Vec3f &v) { this->vel = v; } cv::Vec3f getV() { return vel; } void setAccel(cv::Vec3f &a) { accel = a; } cv::Vec3f getAccel() { return accel; } void setR(double r) { this->r = r; } double getR() { return r; } void step() { vel += accel; pos += vel; } bool bounce(pair<cv::Vec3f, cv::Vec3f> &seg) { cv::Vec3f nearPt = nearest(seg, pos); double len = cv::norm(pos, nearPt); cv::Vec3f oldPos = pos - vel; pair<cv::Vec3f, cv::Vec3f> trail(oldPos, pos); pair<cv::Vec3f, cv::Vec3f> pr = nearest(seg, trail); double len2 = cv::norm(pr.first, pr.second); if (len2 < EPS) { cv::Vec3f pt = oldPos - pr.second; cv::Vec3f par = seg.second - seg.first; cv::Vec3f normal = par.cross(cv::Vec3f(0, 0, 1)); normal /= cv::norm(normal); double cs = pt.dot(normal); if (cs < 0){ normal *= -1.0; cs *= -1.0; } vel = 2 * cs * normal - pt; pos += vel; return true; } else if (len < r) { reflect(seg, nearPt); return true; } return false; } bool bounce(Ball &ball) { cv::Vec3f normal = pos - ball.pos; double len = cv::norm(normal); if (len >= r + ball.r) return false; if (len < 1.0) { pos[0] = -1; normal = pos - ball.pos; len = cv::norm(normal); } normal /= len; cv::Vec3f u = -vel; double cs = normal.dot(u); if (cs > 0) vel = 2 * cs * normal - u; pos += (r + ball.r - len) / 2 * normal; normal *= -1.0; u = -ball.vel; cs = normal.dot(u); if (cs > 0) ball.vel = 2 * cs * normal - u; ball.pos += (r + ball.r - len) / 2 * normal; return true; } pair<cv::Vec3f, cv::Vec3f> nearest(pair<cv::Vec3f, cv::Vec3f> &seg1, pair<cv::Vec3f, cv::Vec3f> &seg2) { pair<cv::Vec3f, cv::Vec3f> ans; cv::Vec3f ab = seg1.second - seg1.first, cd = seg2.second - seg2.first, ac = seg2.first - seg1.first; cv::Vec3f v = ab.cross(cd); if (v[2] > EPS) { cv::Vec3f u = ac.cross(cd), w = ac.cross(ab); double t = u[2] / v[2], s = w[2] / v[2]; if (t >= 0 - EPS && t <= 1.0 + EPS && s >= 0 - EPS && s <= 1.0 + EPS) { if (abs(t) < EPS) { ans.first = ans.second = seg1.first; } else if (abs(t - 1) < EPS) { ans.first = ans.second = seg1.second; } else if (abs(s) < EPS) { ans.first = ans.second = seg2.first; } else if (abs(s - 1) < EPS) { ans.first = ans.second = seg2.second; } else { ab[2] = 0.0; ans.first = ans.second = ab*t + seg1.first; } return ans; } } ans.first = seg1.first; ans.second = nearest(seg2, seg1.first); double minlen = cv::norm(ans.second, seg1.first); v = nearest(seg2, seg1.second); double len = cv::norm(v, seg1.second); if (len < minlen) { ans.first = seg1.first; ans.second = v; minlen = len; } v = nearest(seg1, seg2.first); len = cv::norm(v, seg2.first); if (len < minlen) { ans.first = v; ans.second = seg2.first; minlen = len; } v = nearest(seg1, seg2.second); len = cv::norm(v, seg2.second); if (len < minlen) { ans.first = v; ans.second = seg2.second; minlen = len; } return ans; } double minDistance(pair<cv::Vec3f, cv::Vec3f> &seg, cv::Vec3f &pt) { cv::Vec3f nearPt = nearest(seg, pt); return cv::norm(nearPt, pt); } cv::Vec3f nearest(pair<cv::Vec3f, cv::Vec3f> &seg, cv::Vec3f &pt) { cv::Vec3f p(seg.second - seg.first), q(pt - seg.first); double t = p.dot(q) / p.dot(p); if (t <= 0.0 + EPS) return seg.first; if (t >= 1.0 - EPS) return seg.second; p = p*t + seg.first; return p; } void reflect(pair<cv::Vec3f, cv::Vec3f> &seg, cv::Vec3f &nearPt) { cv::Vec3f normal = pos - nearPt; double len = cv::norm(normal); if (len < 1.0) { // if ball's center is on the segment, add 2 to ball.y pos[1] -= 2; normal = pos - nearPt; len = cv::norm(normal); } normal /= len; if (len<r) pos += (r - len) * normal; if (normal.dot(vel) > 0) { // same direction vel += 5 * normal; } else { cv::Vec3f u = -vel; vel = 2 * normal.dot(u) * normal - u; // reflect } } bool in(cv::Mat &image) { return pos[0] >= 0 && pos[0] < image.cols && pos[1] >= -30 && pos[1] < image.rows; } void draw(cv::Mat &image, int id = -1) { cv::Point pt((int)pos[0], (int)pos[1]); cv::circle(image, pt, r, cv::Scalar(255, 0, 255), -1); stringstream ss; ss << id; if (id >= 0) cv::putText(image, ss.str(), pt, cv::FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(0, 0, 255)); } string tostring() { stringstream ss; ss << "ball[" << pos << " " << vel << " " << accel << " " << r << "]"; return ss.str(); } }; |
プロジェクトのソースを置くフォルダに BallFall.h をコピーしてから、プロジェクトに加えます。
BallFall.h |
#pragma once #include <time.h> #include <opencv2/opencv.hpp> #include "Ball.h" using namespace std; class BallFall { #define N_BALL 8 private: cv::Mat image; vector<Ball> balls; cv::Vec3f mouse; public: BallFall() { srand((unsigned int)time(NULL)); } ~BallFall() {} void start(int w = 640, int h = 480) { image = cv::Mat(h, w, CV_8UC3); for (int i = 0; i < N_BALL; i++) { Ball ball; randomBall(ball); balls.push_back(ball); } cv::namedWindow("ball fall"); mouse[0] = -1; } void randomBall(Ball &b) { cv::Vec3f pos, v, accel; pos[0] = rand() % image.cols; pos[1] = 0; pos[2] = 0; v[0] = rand() % 4 - 2; v[1] = rand() % 1; v[2] = 0; accel[0] = 0; accel[1] = 1.0; accel[2] = 0; b.setPos(pos); b.setV(v); b.setAccel(accel); b.setR(30); } void step(vector<pair<cv::Vec3f, cv::Vec3f>>& segments) { for (int i = 0; i < balls.size(); i++) balls[i].step(); for (int i = 0; i < balls.size(); i++) { for (int j = i + 1; j < balls.size(); j++) balls[i].bounce(balls[j]); } for (int i = 0; i < balls.size(); i++) { for (int j = 0; j < segments.size(); j++) balls[i].bounce(segments[j]); if (!balls[i].in(image)) randomBall(balls[i]); } } void draw(vector<pair<cv::Vec3f, cv::Vec3f>>& segments) { cv::rectangle(image, cv::Rect(0, 0, image.cols, image.rows), cv::Scalar(50, 50, 50), -1); draw(segments, image); cv::imshow("ball fall", image); } void draw(vector<pair<cv::Vec3f, cv::Vec3f>>& segments, cv::Mat& image) { for (int i = 0; i < balls.size(); i++) { balls[i].draw(image); } for (int i = 0; i < segments.size(); i++) { cv::Point p((int)segments[i].first[0], (int)segments[i].first[1]); cv::Point q((int)segments[i].second[0], (int)segments[i].second[1]); cv::line(image, p, q, cv::Scalar(255, 255, 0), 8); } } }; |
main.cpp |
#include <iostream> #include <sstream> #include "NtKinect.h" #include "BallFall.h" const int segment[] = { JointType_SpineBase, JointType_SpineMid, // spine JointType_SpineMid, JointType_SpineShoulder, JointType_SpineShoulder, JointType_Neck, // head JointType_Neck, JointType_Head, JointType_SpineShoulder, JointType_ShoulderLeft, // left hand JointType_ShoulderLeft, JointType_ElbowLeft, JointType_ElbowLeft, JointType_WristLeft, JointType_WristLeft, JointType_HandLeft, JointType_HandLeft, JointType_HandTipLeft, JointType_HandLeft, JointType_ThumbLeft, JointType_SpineShoulder, JointType_ShoulderRight, // right hand JointType_ShoulderRight, JointType_ElbowRight, JointType_ElbowRight, JointType_WristRight, JointType_WristRight, JointType_HandRight, JointType_HandRight, JointType_HandTipRight, JointType_HandRight, JointType_ThumbRight, JointType_SpineBase, JointType_HipLeft, // left leg JointType_HipLeft, JointType_KneeLeft, JointType_KneeLeft, JointType_AnkleLeft, JointType_AnkleLeft, JointType_FootLeft, JointType_SpineBase, JointType_HipRight, // right leg JointType_HipRight, JointType_KneeRight, JointType_KneeRight, JointType_AnkleRight, JointType_AnkleRight, JointType_FootRight, }; const int segmentSize = sizeof(segment) / sizeof(int); using namespace std; void doJob() { NtKinect kinect; BallFall bf; bool flag = false; kinect.setRGB(); bf.start(kinect.rgbImage.cols, kinect.rgbImage.rows); vector<pair<cv::Vec3f, cv::Vec3f>> skel; while (1) { kinect.setRGB(); kinect.setSkeleton(); skel.clear(); for (int i = 0; i < kinect.skeleton.size(); i++) { for (int j = 0; j < segmentSize/2; j++) { Joint joint1 = kinect.skeleton[i][segment[j * 2]]; Joint joint2 = kinect.skeleton[i][segment[j * 2 + 1]]; if (joint1.TrackingState != TrackingState_NotTracked && joint2.TrackingState != TrackingState_NotTracked) { ColorSpacePoint cp; kinect.coordinateMapper->MapCameraPointToColorSpace(joint1.Position, &cp); cv::Vec3f p1 = cv::Vec3f(cp.X, cp.Y, 0.0f); kinect.coordinateMapper->MapCameraPointToColorSpace(joint2.Position, &cp); cv::Vec3f p2 = cv::Vec3f(cp.X, cp.Y, 0.0f); skel.push_back(pair<cv::Vec3f, cv::Vec3f>(p1,p2)); } } } bf.step(skel); cv::Mat image; if (flag) image = kinect.rgbImage.clone(); else { image = cv::Mat(kinect.rgbImage.rows,kinect.rgbImage.cols,CV_8UC3); cv::rectangle(image,cv::Rect(0,0,image.cols,image.rows),cv::Scalar(0,0,0), -1); } bf.draw(skel, image); cv::imshow("ball fall", image); auto key = cv::waitKey(1); if (key == 'q') break; if (key == 'v') flag = !flag; } } int main(int argc, char** argv) { try { doJob(); } catch (exception &ex) { cout << ex.what() << endl; string s; cin >> s; } return 0; } |
上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。