updated
7
API/games/circle.js
Normal file
@@ -0,0 +1,7 @@
|
||||
exports.newCircle = function(center, radius, velocity) {
|
||||
return {
|
||||
center: center,
|
||||
radius: radius,
|
||||
velocity: velocity
|
||||
}
|
||||
}
|
||||
32
API/games/vector.js
Normal file
@@ -0,0 +1,32 @@
|
||||
exports.createVector = function(x, y) {
|
||||
return {x: x, y: y};
|
||||
}
|
||||
|
||||
exports.mag = function(v){
|
||||
return Math.sqrt(exports.dot(v, v));
|
||||
}
|
||||
|
||||
exports.add = function(v1, v2){
|
||||
return {x:v1.x + v2.x, y:v1.y + v2.y};
|
||||
}
|
||||
|
||||
exports.sub = function(v1, v2){
|
||||
return {x: v1.x - v2.x, y:v1.y - v2.y};
|
||||
}
|
||||
|
||||
exports.dot = function(v1, v2){
|
||||
return v1.x*v2.x + v1.y*v2.y;
|
||||
}
|
||||
|
||||
exports.mult = function(v1, k){
|
||||
return {x: v1.x*k, y:v1.y*k};
|
||||
}
|
||||
|
||||
exports.reflect = function(v1, n){
|
||||
return exports.sub(exports.mult(exports.project(v1, n), 2), v1);
|
||||
}
|
||||
|
||||
exports.project = function(v1, v2){
|
||||
return exports.mult(v2, (1/Math.pow(exports.mag(v2), 2)) * exports.dot(v1, v2));
|
||||
}
|
||||
|
||||
195
API/games/volleyball.js
Normal file
@@ -0,0 +1,195 @@
|
||||
//import * as posenet from "@tensorflow-models/posenet";
|
||||
let posenet = require("@tensorflow-models/posenet");
|
||||
//var circle = require("./circle")
|
||||
let vec = require("./vector");
|
||||
var collide = require('line-circle-collision');
|
||||
|
||||
let prevTime = 0;
|
||||
|
||||
exports.process = function(game, players) {
|
||||
if (Object.keys(game.data).length == 0) {
|
||||
initialize(game);
|
||||
}
|
||||
let timeElapsed = Date.now() - prevTime;
|
||||
prevTime = Date.now();
|
||||
collideAll(game.data.objects, players);
|
||||
moveItems(game.data.objects, timeElapsed);
|
||||
}
|
||||
|
||||
function initialize(game){
|
||||
prevTime = Date.now();
|
||||
game.data = {
|
||||
objects: []
|
||||
}
|
||||
for (let i = 0; i < 50; i++){
|
||||
game.data.objects.push({
|
||||
name: "circle",
|
||||
position: vec.createVector(20*i, -100),
|
||||
radius: 20,
|
||||
velocity: vec.createVector(0, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function moveItems(objects, timeElapsed) {
|
||||
let acceleration = vec.createVector(0, 0.0002);
|
||||
for (let i = 0; i < objects.length; i++){
|
||||
objects[i].velocity = vec.add(objects[i].velocity, vec.mult(acceleration, timeElapsed))
|
||||
objects[i].position = vec.add(objects[i].position, vec.mult(objects[i].velocity, timeElapsed))
|
||||
|
||||
if (objects[i].position.y > 500 && objects[i].velocity.y > 0) {
|
||||
objects[i].velocity.y *= -1;
|
||||
}
|
||||
// if (objects[i].position.x < 0 && objects[i].velocity.x < 0) {
|
||||
// objects[i].velocity.x *= -1;
|
||||
// }
|
||||
// if (objects[i].position.x > 1000 && objects[i].velocity.x > 0) {
|
||||
// objects[i].velocity.x *= -1;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
function collideWithJoint(object, [start, end]){
|
||||
let diff = vec.sub(end, start);
|
||||
object.velocity = vec.mult(vec.reflect(object.velocity, {x:-diff.y, y:diff.x}), -1);
|
||||
object.position = vec.add(object.position, vec.mult(object.velocity, 3));
|
||||
}
|
||||
|
||||
/*
|
||||
function pointInCircle(x1, y1, cx, cy, cr){
|
||||
let distance = (x1-cx)*(x1-cx)+(y1-cy)*(y1-cy);
|
||||
distance = Math.sqrt(distance);
|
||||
return distance <= cr;
|
||||
}
|
||||
*/
|
||||
|
||||
function pointOnLine(point, start, end){
|
||||
if (point.x < start.x && point.x > end.x || point.x > start.x && point.x < end.x){
|
||||
if (point.y > start.y && point.y < end.y || point.y > end.y && point.y < start.y){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// if (vec.mag(vec.sub(point, start)) > vec.mag(vec.sub(end, start))) {
|
||||
// return false;
|
||||
// }
|
||||
// if (vec.mag(vec.sub(point, end)) > vec.mag(vec.sub(start, end))) {
|
||||
// return false;
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
// is either end INSIDE the circle?
|
||||
// if so, return true immediately
|
||||
|
||||
|
||||
function overlappingWithJoint(object, start, end){
|
||||
// let cx = object.position.x;
|
||||
// let cy = object.position.y;
|
||||
// let r = object.radius;
|
||||
|
||||
// let x1 = start.x;
|
||||
// let y1 = start.y;
|
||||
|
||||
// let x2 = end.x;
|
||||
// let y2 = end.y;
|
||||
|
||||
// let inside1 = pointCircle(x1,y1, cx,cy,r);
|
||||
// let inside2 = pointCircle(x2,y2, cx,cy,r);
|
||||
// if (inside1 || inside2) return true;
|
||||
|
||||
// // get length of the line
|
||||
// let distX = x1 - x2;
|
||||
// let distY = y1 - y2;
|
||||
// let len = Math.sqrt( (distX*distX) + (distY*distY) );
|
||||
|
||||
// // get dot product of the line and circle
|
||||
// let dot = ( ((cx-x1)*(x2-x1)) + ((cy-y1)*(y2-y1)) ) / Math.pow(len,2);
|
||||
|
||||
// // find the closest point on the line
|
||||
// let closestX = x1 + (dot * (x2-x1));
|
||||
// let closestY = y1 + (dot * (y2-y1));
|
||||
|
||||
// // is this point actually on the line segment?
|
||||
// // if so keep going, but if not, return false
|
||||
// let onSegment = linePoint(x1,y1,x2,y2, closestX,closestY);
|
||||
// if (!onSegment) return false;
|
||||
|
||||
// // optionally, draw a circle at the closest
|
||||
// // point on the line
|
||||
// // fill(255,0,0);
|
||||
// // noStroke();
|
||||
// // ellipse(closestX, closestY, 20, 20);
|
||||
|
||||
// // get distance to closest point
|
||||
// distX = closestX - cx;
|
||||
// distY = closestY - cy;
|
||||
// let distance = Math.sqrt( (distX*distX) + (distY*distY) );
|
||||
|
||||
// if (distance <= r) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
|
||||
// let pos = object.position;
|
||||
|
||||
// // if (pointInCircle(end.x, end.y, pos.x, pos.y, object.radius)){
|
||||
// // return true;
|
||||
// // }
|
||||
// // if (pointInCircle(start.x, start.y, pos.x, pos.y, object.radius)){
|
||||
// // return true;
|
||||
// // }
|
||||
|
||||
// let diff = vec.sub(end, start);
|
||||
// let len = vec.mag(diff);
|
||||
// let proj = vec.project(vec.sub(end, pos), diff);
|
||||
// let point = vec.sub(end, proj);
|
||||
|
||||
// console.log("diff: ")
|
||||
// console.log(diff)
|
||||
// console.log("proj")
|
||||
// console.log(proj)
|
||||
// console.log("new")
|
||||
|
||||
// //console.log(point);
|
||||
|
||||
// if (!pointOnLine(point, start, end)){
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// //let dist = Math.sqrt(Math.pow(vec.mag(vec.sub(end, pos))) - Math.pow(vec.mag(proj)))
|
||||
// let dist = vec.mag(vec.sub(point, pos));
|
||||
// return dist <= object.radius;
|
||||
|
||||
// //console.log("overlapping detected");
|
||||
// // let diff = vec.sub(end, start);
|
||||
// // let pos = object.position;
|
||||
// // let diff2 = vec.sub(end, pos);
|
||||
// // let r = vec.project(diff2, diff);
|
||||
// // let dist = Math.sqrt(vec.dot(pos, pos) + vec.dot(r, r));
|
||||
// // let midPoint = vec.mult(vec.add(start, end), 0.5);
|
||||
|
||||
// // return Math.abs(dist) <= object.radius;
|
||||
return collide([start.x, start.y], [end.x, end.y], [object.position.x, object.position.y], object.radius);
|
||||
}
|
||||
|
||||
function collideAll(objects, players){
|
||||
let minConfidence = 0.5;
|
||||
if (playes){
|
||||
players.forEach(player => {
|
||||
if (player.universePairs){
|
||||
console.log("sdfsdfg");
|
||||
player.universePairs.forEach(pair => {
|
||||
let start = vec.createVector(pair.x1, pair.y1);
|
||||
let end = vec.createVector(pair.x2, pair.y2);
|
||||
for (let i = 0; i < objects.length; i++){
|
||||
let object = objects[i];
|
||||
if (overlappingWithJoint(object, start, end)){
|
||||
collideWithJoint(object, [start, end]);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
107
API/index.js
@@ -2,41 +2,114 @@ const express = require('express');
|
||||
const app = express();
|
||||
const webSockets = express();
|
||||
const bodyParser = require('body-parser');
|
||||
const PORT = 4000;
|
||||
const cors = require('cors');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
const mongoose = require('mongoose');
|
||||
const socket = require('socket.io');
|
||||
const config = require('./DB.js');
|
||||
const roomRoute = require('./room.route');
|
||||
var expressWs = require('express-ws')(app);
|
||||
|
||||
// DB credentials
|
||||
const config = require('./DB.js');
|
||||
|
||||
// Routes
|
||||
const roomRoute = require('./room.route');
|
||||
|
||||
// All active rooms, store in map since faster than DB
|
||||
var rooms = new Map();
|
||||
|
||||
// For SSL Connection
|
||||
var privateKey = fs.readFileSync('/etc/letsencrypt/live/dance.cubehostingmc.com/privkey.pem', 'utf8');
|
||||
var certificate = fs.readFileSync('/etc/letsencrypt/live/dance.cubehostingmc.com/fullchain.pem', 'utf8');
|
||||
var credentials = {key: privateKey, cert: certificate};
|
||||
|
||||
// DB connection
|
||||
mongoose.Promise = global.Promise;
|
||||
mongoose.connect(config.DB, { useNewUrlParser: true }).then(
|
||||
() => { console.log('Database is connected') },
|
||||
err => { console.log('Can not connect to the database' + err) }
|
||||
);
|
||||
|
||||
// Express config
|
||||
app.use(cors());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use('/rooms', roomRoute);
|
||||
var httpServer = http.createServer(app);
|
||||
var httpsServer = https.createServer(credentials, app);
|
||||
|
||||
app.listen(PORT, function () {
|
||||
console.log('Express server running on port:', PORT);
|
||||
})
|
||||
//games
|
||||
//import * as volleyball from 'games/volleyball.mjs';
|
||||
|
||||
app.ws('/:id', function (ws, req) {
|
||||
var volleyball = require('./games/volleyball');
|
||||
|
||||
ws.on('message', function (msg) {
|
||||
msgJ = JSON.parse(msg);
|
||||
console.log(req.params.id);
|
||||
console.log(msgJ.playerId);
|
||||
ws.send(msg);
|
||||
// Websocket config
|
||||
const wss = new WebSocket.Server({ server: httpsServer }); // Use SSL Server
|
||||
|
||||
// On WebSocket Connection
|
||||
wss.on('connection', function connection(ws) {
|
||||
// On Message Recieve
|
||||
ws.on('message', function incoming(message) {
|
||||
let msgJson = JSON.parse(message); // Convert text to JSON
|
||||
|
||||
|
||||
|
||||
// Process Player Positions
|
||||
if (rooms.has(msgJson.roomId)) { // Check if the room already exists in the map
|
||||
// Process Game Data
|
||||
//TODO: implement a generic way to have the gameData sent to the right file
|
||||
if (msgJson.game.name == "0"){
|
||||
//console.log(msgJson.playerArr);
|
||||
volleyball.process(rooms.get(msgJson.roomId).game, msgJson.playerArr);
|
||||
}
|
||||
let changed = false; // To see if the player already exists in the arena
|
||||
for (let i = 0; i < rooms.get(msgJson.roomId).playerArr.length; i++) {
|
||||
// Update existing player positions
|
||||
if (rooms.get(msgJson.roomId).playerArr[i].playerId == msgJson.playerArr[0].playerId) {
|
||||
//console.log(msgJson.name);
|
||||
let newRoom = rooms.get(msgJson.roomId);
|
||||
newRoom.playerArr[i] = msgJson.playerArr[0];
|
||||
newRoom.playerArr[i].colour = msgJson.colour;
|
||||
newRoom.playerArr[i].name = msgJson.name;
|
||||
rooms.set(msgJson.roomId, newRoom);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
// Create new player positions
|
||||
if(!changed) {
|
||||
let newRoom = rooms.get(msgJson.roomId);
|
||||
newRoom.playerArr.push(msgJson.playerArr[0]);
|
||||
newRoom.playerArr[newRoom.playerArr.length-1].session = ws.sessionIdContext; // Required for handling socket closing
|
||||
newRoom.playerArr[newRoom.playerArr.length-1].colour = msgJson.colour;
|
||||
newRoom.playerArr[newRoom.playerArr.length-1].name = msgJson.name;
|
||||
rooms.set(msgJson.roomId, newRoom);
|
||||
}
|
||||
} else {
|
||||
|
||||
msgJson.game.data = {};
|
||||
rooms.set(msgJson.roomId, msgJson); // Create the room
|
||||
}
|
||||
ws.send(JSON.stringify(rooms.get(msgJson.roomId))); // Send the updated room data
|
||||
});
|
||||
|
||||
// On websocket close
|
||||
ws.onclose = function (event) {
|
||||
console.log("Client disconnected from: " + req.params.id);
|
||||
console.log("Client disconnected");
|
||||
// Go through each existing room entry to find the websocket with the session that was closed with
|
||||
for (const [key, value] of rooms.entries()) {
|
||||
for (let i = 0; i < value.playerArr.length; i++){
|
||||
if (value.playerArr[i].session == ws.sessionIdContext) {
|
||||
newRoom = value;
|
||||
newRoom.playerArr.splice(i); // Remove player with index i
|
||||
rooms.set(key, newRoom);
|
||||
}
|
||||
// TODO: Add code to remove the room from both DB and Map if
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
console.log("Client connected to: " + req.params.id);
|
||||
});
|
||||
|
||||
// Host both an http and https server
|
||||
httpServer.listen(4001);
|
||||
httpsServer.listen(4000);
|
||||
6295
API/package-lock.json
generated
@@ -13,7 +13,10 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"line-circle-collision": "^1.1.3",
|
||||
"mongoose": "^5.11.17",
|
||||
"node-p5": "^1.0.3",
|
||||
"p5": "^1.2.0",
|
||||
"socket.io": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -5,6 +5,15 @@ const Schema = mongoose.Schema;
|
||||
let Player = new Schema({
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
session: {
|
||||
type: String
|
||||
},
|
||||
colour: {
|
||||
type: Number
|
||||
},
|
||||
score: {
|
||||
type: Number
|
||||
}
|
||||
}, {
|
||||
collection: 'players'
|
||||
|
||||
@@ -8,6 +8,9 @@ let Room = new Schema({
|
||||
},
|
||||
members: {
|
||||
type: Array
|
||||
},
|
||||
game: {
|
||||
type: String
|
||||
}
|
||||
}, {
|
||||
collection: 'rooms'
|
||||
|
||||
@@ -8,12 +8,15 @@ let Player = require('./player.model');
|
||||
postRoutes.route('/add').get(function (req, res) {
|
||||
let r = new Room();
|
||||
let p = new Player();
|
||||
p.colour = Math.trunc(Math.random()*8);
|
||||
p.score = 0;
|
||||
r.members.push(p);
|
||||
r.save()
|
||||
.then(() => {
|
||||
let ret = {};
|
||||
ret._id = r._id;
|
||||
ret.playerId = p._id;
|
||||
ret.colour = p.colour;
|
||||
res.send(ret);
|
||||
console.log("Created Room");
|
||||
})
|
||||
@@ -31,39 +34,26 @@ postRoutes.route('/join').post(function (req, res) {
|
||||
res.json(err);
|
||||
console.log("Error Joining Room");
|
||||
} else {
|
||||
if (r) {
|
||||
let p = new Player();
|
||||
p.colour = Math.trunc(Math.random()*8);
|
||||
p.score = 0;
|
||||
r.members.push(p);
|
||||
r.save().then(() => {
|
||||
res.send(p);
|
||||
console.log(p);
|
||||
console.log("Joined Room");
|
||||
console.log(r.members);
|
||||
}).catch(() => {
|
||||
console.log("Unable to save to db");
|
||||
res.status(400).send("Unable to save to the database")
|
||||
})
|
||||
} else {
|
||||
res.send(null);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
// Defined get data(index or listing) route
|
||||
postRoutes.route('/').get(function (req, res) {
|
||||
Post.find(function (err, posts) {
|
||||
if (err) {
|
||||
res.json(err);
|
||||
}
|
||||
else {
|
||||
res.json(posts);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Defined delete | remove | destroy route
|
||||
postRoutes.route('/delete/:id').delete(function (req, res) {
|
||||
Post.findByIdAndRemove({ _id: req.params.id }, function (err) {
|
||||
if (err) res.json(err);
|
||||
else res.json('Successfully removed');
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = postRoutes;
|
||||
22215
package-lock.json
generated
15
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "stick-figure-game",
|
||||
"name": "posenet-test",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -12,13 +12,14 @@
|
||||
"@tensorflow/tfjs": "^2.8.6",
|
||||
"@tensorflow/tfjs-converter": "^1.7.4",
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap": "^4.6.0",
|
||||
"core-js": "^3.6.5",
|
||||
"socket.io": "^3.1.1",
|
||||
"socket.io-client": "^3.1.1",
|
||||
"node-p5": "^1.0.3",
|
||||
"p5": "^1.2.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-axios": "^3.2.4",
|
||||
"vue-router": "^3.5.1",
|
||||
"vue-socket.io": "^3.0.10"
|
||||
"vue-window-size": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss7-compat": "^2.0.3",
|
||||
@@ -30,10 +31,10 @@
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"postcss": "^7.0.35",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"sass": "^1.22.3",
|
||||
"sass-loader": "^7.1.0"
|
||||
"sass-loader": "^7.1.0",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -5,12 +5,11 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script>
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<title>ChillSpace</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
<strong>We're sorry but Party With Friends doesn't work without JavaScript enabled.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
|
||||
BIN
public/photos/background.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
23
server.cert
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDvzCCAqegAwIBAgIUAimJ6DpDaSAz2e2O284vuyav6lMwDQYJKoZIhvcNAQEL
|
||||
BQAwbzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xEDAOBgNVBAcMB1Rv
|
||||
cm9udG8xDDAKBgNVBAoMA04vQTEMMAoGA1UECwwDTi9BMQwwCgYDVQQDDANOL0Ex
|
||||
EjAQBgkqhkiG9w0BCQEWA04vQTAeFw0yMTAyMjAxMDMyNDBaFw0yMTAzMjIxMDMy
|
||||
NDBaMG8xCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU
|
||||
b3JvbnRvMQwwCgYDVQQKDANOL0ExDDAKBgNVBAsMA04vQTEMMAoGA1UEAwwDTi9B
|
||||
MRIwEAYJKoZIhvcNAQkBFgNOL0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||
AoIBAQDg1EzFmnZBdB9gclrdpcyTM2OQlOyur5LHvZ56eiIqQhnkqC3fTtEMvylM
|
||||
34P9q/zWqJUlbPbxhIl8K3QNKGxX5PIQiZqoG2yixZO7ixxIH6E0kzeM8arkWTHo
|
||||
taFq9y0zg2RBBDwa5l20+YZWfWE4ph6tqnp81vpcGheN++q6hUmRiyfIigrGoQpI
|
||||
YXD9L5VPV1r6MIdMY8xWR7lgVH/IvNv7zv8H/lPw7POlvHYPZ9Csg7FrqU1fYubG
|
||||
9pK9Z3PxHU/IWkrLp4JE58rOTIVrVEqfktzSHrft8FZjUu+Lefk/V5AjM4br0WEc
|
||||
M0szXWo5NK7QXzC1bYgTdwEsSmcLAgMBAAGjUzBRMB0GA1UdDgQWBBRuN1ro+DPR
|
||||
3rk/X/P6Fklbwbr3DDAfBgNVHSMEGDAWgBRuN1ro+DPR3rk/X/P6Fklbwbr3DDAP
|
||||
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCKGJZ3DqHLEUZeQ5VO
|
||||
LHpgP97Yq3wrdgbR45jeufu0BtGVXzc9nT+9d0Dm0C5IdVtsXQ5fYXBQzY5xzyj0
|
||||
OltoHXcEDr9P0E9QponFV1PQuajEYfzu7gSXaGhbGEKknaY0VOmdMfRa9cIW6sGP
|
||||
uo0zuoXglqWj7287WPgdbcVk76rOfWCKJqISagh/f/C70zL0LBgcSFF1CcFq9uc3
|
||||
korhjtduCJutyQHlvMHhlWBdjFpVmB7ndGPTcirIsb8qAAD+BxBWsQnhgUyj8H+z
|
||||
IbaIwSh6dlkAfvn4129OlzidY4Q+nDaK3knfgouLuSH7d/cCZsGrq54o9KJjVKw0
|
||||
eMey
|
||||
-----END CERTIFICATE-----
|
||||
28
server.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDg1EzFmnZBdB9g
|
||||
clrdpcyTM2OQlOyur5LHvZ56eiIqQhnkqC3fTtEMvylM34P9q/zWqJUlbPbxhIl8
|
||||
K3QNKGxX5PIQiZqoG2yixZO7ixxIH6E0kzeM8arkWTHotaFq9y0zg2RBBDwa5l20
|
||||
+YZWfWE4ph6tqnp81vpcGheN++q6hUmRiyfIigrGoQpIYXD9L5VPV1r6MIdMY8xW
|
||||
R7lgVH/IvNv7zv8H/lPw7POlvHYPZ9Csg7FrqU1fYubG9pK9Z3PxHU/IWkrLp4JE
|
||||
58rOTIVrVEqfktzSHrft8FZjUu+Lefk/V5AjM4br0WEcM0szXWo5NK7QXzC1bYgT
|
||||
dwEsSmcLAgMBAAECggEAbZYNdaO8UFsRkCktMSxHcthxh1+PnfXmTYWXYYt2a7Kc
|
||||
fF+dPGNmchgbQWURKOxT6S1yxTM/iqlXejaT8nXpmU32zyjDQX1cjlpPj/JWbrE8
|
||||
GixHv2NsMLFpKkydyU2BssIwc6Bky5fNxRB68l1argoKmgumeIMwtQPix0orN44m
|
||||
FMi+Tn9k8BV4P9U4mvbixAmXNWeTkqPp6exYNS5UB3pLliPDl25vkiyMKm4BZi5P
|
||||
haUkOQxNhJgtrutQUiMvk0/TEKieXMlBP7BtoD2QJUnzBwimd00hxdv69Vtbdvjl
|
||||
K3PjVcRpsfhyVIGXsiVJkiv10LtuhaInic+HGIDmgQKBgQD7VI+TZAWXzHCUkqtI
|
||||
eFa5p9Q+v89czOIRokN0n6e/xmWIGoW1Tcdp4evvq4fwAaG5IsL2QEtyr+5kqXDX
|
||||
yL/84/IQ3cDuEb5bDRj4JDITSFwD+uAs7WsSHnxalU9QsWQoYwYMUDMeXuzsNt40
|
||||
utcdisJklSnLWtapIkAIDP+LUwKBgQDlAbA5GlulcBKESO71SrOQG1P2N7JS/8yj
|
||||
1RzOdBKM7ecbVF90+j8QhWLdMptDytfOKXuq1dY0TpmcaJb7iX6cohs7ENgzM8qF
|
||||
99/l9PX2gWPq9Hz7KVZKrtaEezHWvTMlY8dtTr16xWwVZKC03LEpKuhy56kPb1s0
|
||||
gBBvN3Z2aQKBgQCASU1wAmIIdcYRUDw1pZc+9LeVv/psd/f84EJmSQgrD23L5x3Q
|
||||
yX68QSFZGMkubObLxT6Wy4K3a63Xm9WJj2LQBtoMWeScoFn1x92y62bUCN9O+MNs
|
||||
q1M30G/RHN17ZtCk/MadimJNYk6009zgNW6QGb/X73uB3UAs3NqqQVg/GQKBgBfK
|
||||
8h0ssOLiXvohfbo8daV+QV3ucXeQHhnTdXe5tYew2/cJ8BT+PvkfcqMas+j6NSu1
|
||||
QaUBLI0osWr/rtgZc+8gJIYhfOTs95itpTSGG9vtm4z4s9eAdvexbJY9GnN+GsdC
|
||||
s6CWsrcDtfQPDWddGob1b0so6HazEh3FRG/ZqlQpAoGADL6Dew4qokh4t+U3ocJu
|
||||
1Aih0EcZEYHmd4jfp5up+4G0B7LV2/vgvstJJCbkQ8zuySzIVLVLOo068m4IgsJr
|
||||
gNoMJtzWosraONM9pPaE/xylzSWD+m+JNWimRgAqsWwOA9VhIyVzK7HKueDkWGYp
|
||||
oXbdgzI/46r7UoIjrXrduI4=
|
||||
-----END PRIVATE KEY-----
|
||||
10
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,8 +13,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.app{
|
||||
background-color: #f2f2f2;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
src/assets/dance.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/logo.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
0
src/assets/reset.css
Normal file
BIN
src/assets/soccer.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/volleyball.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
@@ -1,52 +1,200 @@
|
||||
<template>
|
||||
<div>
|
||||
<button v-on:click="sendMessage()">Send Message</button>
|
||||
<video ref="video" autoplay></video>
|
||||
<canvas ref="canvas"></canvas>
|
||||
<!-- The Modal -->
|
||||
<div id="myModal" class="modal">
|
||||
<!-- Modal content -->
|
||||
<div class="modal-content">
|
||||
<p>Loading video...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<br />
|
||||
<br />
|
||||
<center><h2>Your Room </h2>
|
||||
<h4 class="text-muted">Code: {{message.roomId}}</h4></center>
|
||||
<div id="canvas-div" style="text-align: center">
|
||||
<canvas
|
||||
ref="canvas"
|
||||
class="top"
|
||||
id="2d"
|
||||
v-bind:width="windowWidth - 30"
|
||||
height="500"
|
||||
style="
|
||||
background-color: white;
|
||||
border-style: solid;
|
||||
border-width: 0.5px;
|
||||
border-radius: 15px;
|
||||
height: 50%;
|
||||
background: url('https://i.ibb.co/X3sHrcM/Pngtree-summer-sandy-beach-background-986369.png');
|
||||
">
|
||||
</canvas>
|
||||
</div>
|
||||
<center>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card" style="width: 18rem">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Your Camera</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
(Only you can see this)
|
||||
</h6>
|
||||
<div class="card-text">
|
||||
<div id="video" style="display: inline-block; width: 100%">
|
||||
<video
|
||||
ref="video"
|
||||
autoplay
|
||||
class="top"
|
||||
style="display: inline-block; height: 100%; width: 100%"
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card" style="width: 18rem">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Settings:</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Edit your player:</h6>
|
||||
<div class="card-text">
|
||||
<label for="customRange3" class="form-label"><b>Colour</b></label>
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="1"
|
||||
max="8"
|
||||
step="1"
|
||||
v-model="colorSelect"
|
||||
@change="updateColor"
|
||||
id="customRange3"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
v-model="username"
|
||||
placeholder="Enter a username"
|
||||
@change="updateUsername"
|
||||
/>
|
||||
<br />
|
||||
Color: {{ colorSelect }}
|
||||
<br />
|
||||
Username: {{ username }}
|
||||
<br />
|
||||
Video Loaded: {{ videoLoaded }}
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as posenet from "@tensorflow-models/posenet";
|
||||
import * as utils from "../utils";
|
||||
import { useWindowSize } from "vue-window-size";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const { width, height } = useWindowSize();
|
||||
return {
|
||||
windowWidth: width,
|
||||
windowHeight: height,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
message: {},
|
||||
people: {},
|
||||
resultWidth: 0,
|
||||
resultHeight: 0,
|
||||
colorSelect: 1,
|
||||
username: "",
|
||||
videoLoaded: false,
|
||||
windowWidth: window.innerWidth,
|
||||
modal: {},
|
||||
span: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
console.log("Starting connection to websocket server");
|
||||
console.log(this.$route.params.id);
|
||||
this.connection = new WebSocket(
|
||||
"ws://50.100.180.37:4000/" + this.$route.params.id
|
||||
);
|
||||
//console.log(this.$route.params.id);
|
||||
this.connection = new WebSocket("wss://dance.cubehostingmc.com:4000/");
|
||||
|
||||
this.connection.onopen = function (event) {
|
||||
this.connection.onopen = (event) => {
|
||||
console.log(event);
|
||||
console.log("Successfully connected to the echo WebSocket Server");
|
||||
};
|
||||
|
||||
this.connection.onmessage = function (event) {
|
||||
console.log(event); // When we recieve a message
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.modal = document.getElementById("myModal");
|
||||
this.span = document.getElementsByClassName("close")[0];
|
||||
|
||||
this.modal.style.display = "block";
|
||||
window.addEventListener("resize", () => {
|
||||
this.windowWidth = window.innerWidth;
|
||||
});
|
||||
this.ctx = this.$refs.canvas.getContext("2d");
|
||||
this.net = await posenet.load();
|
||||
this.net = await posenet.load({
|
||||
architecture: "ResNet50",
|
||||
outputStride: 32,
|
||||
inputResolution: 250,
|
||||
multiplier: 1,
|
||||
quantBytes: 2,
|
||||
});
|
||||
this.streamPromise = await this.initWebcamStream();
|
||||
this.detectPose();
|
||||
this.connection.onmessage = (event) => {
|
||||
let data = JSON.parse(event.data);
|
||||
this.game = data.game;
|
||||
this.playerArr = data.playerArr; // When we recieve a message
|
||||
//utils.assignAllUniversePairs(this.playerArr, this.ctx)
|
||||
//let players = this.playerArr;
|
||||
let numPlayers = this.playerArr.length;
|
||||
let width = this.ctx.canvas.width;
|
||||
let x_spacing = width / (numPlayers + 1);
|
||||
for (let i = 0; i < numPlayers; i++) {
|
||||
let x = (i + 1) * x_spacing;
|
||||
let y = this.ctx.canvas.height / 2;
|
||||
let com = utils.getCenterOfMass(this.playerArr[i].pose.keypoints);
|
||||
this.playerArr[i].universePairs = utils.keypointsToUniverse(this.playerArr[i].pose.keypoints, com, [x, y])
|
||||
this.playerArr[i].pose.keypoints.forEach(keypoint => {
|
||||
if (keypoint.part == "nose") {
|
||||
this.playerArr[i].head = utils.getUniversePoint(keypoint, com, [x, y])
|
||||
}
|
||||
})
|
||||
}
|
||||
this.message.playerArr = this.playerArr;
|
||||
};
|
||||
this.gameCycle();
|
||||
},
|
||||
methods: {
|
||||
sendMessage() {
|
||||
console.log(this.connection);
|
||||
//console.log(this.connection);
|
||||
this.message.playerId = this.getCookie("playerId");
|
||||
this.message.gameId = this.$route.params.id;
|
||||
console.log(this.message);
|
||||
this.message.colour = this.getCookie("playerColour");
|
||||
this.message.name = this.username;
|
||||
if (!this.message.game) {
|
||||
this.message.game = {};
|
||||
}
|
||||
utils.assignAllUniversePairs(this.message.playerArr, this.ctx)
|
||||
//this.message.playerArr = this.playerArr;
|
||||
this.message.game.name = this.getCookie("game");
|
||||
//console.log(this.message);
|
||||
let str = JSON.stringify(this.message);
|
||||
|
||||
this.connection.send(str);
|
||||
|
||||
},
|
||||
getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
@@ -69,7 +217,7 @@ export default {
|
||||
return navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: false, // don't capture audio
|
||||
video: { facingMode: "environment" }, // use the rear camera if there is
|
||||
video: { facingMode: "user" }, // use the rear camera if there is
|
||||
})
|
||||
.then((stream) => {
|
||||
// set <video> source as the webcam input
|
||||
@@ -83,6 +231,8 @@ export default {
|
||||
return new Promise((resolve) => {
|
||||
// when video is loaded
|
||||
video.onloadedmetadata = () => {
|
||||
this.videoLoaded = true;
|
||||
this.modal.style.display = "none";
|
||||
// calculate the video ratio
|
||||
this.videoRatio = video.offsetHeight / video.offsetWidth;
|
||||
// add event listener on resize to reset the <video> and <canvas> sizes
|
||||
@@ -105,43 +255,119 @@ export default {
|
||||
);
|
||||
}
|
||||
},
|
||||
setResultSize () {
|
||||
setResultSize() {
|
||||
// get the current browser window size
|
||||
let clientWidth = document.documentElement.clientWidth
|
||||
let clientWidth = document.documentElement.clientWidth;
|
||||
// set max width as 600
|
||||
this.resultWidth = Math.min(600, clientWidth)
|
||||
this.resultWidth = Math.min(600, clientWidth);
|
||||
// set the height according to the video ratio
|
||||
this.resultHeight = this.resultWidth * this.videoRatio
|
||||
this.resultHeight = this.resultWidth * this.videoRatio;
|
||||
// set <video> width and height
|
||||
/*
|
||||
Doesn't use vue binding :width and :height,
|
||||
because the initial value of resultWidth and resultHeight
|
||||
will affect the ratio got from the initWebcamStream()
|
||||
*/
|
||||
let video = this.$refs.video
|
||||
video.width = this.resultWidth
|
||||
video.height = this.resultHeight
|
||||
let video = this.$refs.video;
|
||||
video.width = this.resultWidth;
|
||||
video.height = this.resultHeight;
|
||||
},
|
||||
timeout(ms){
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
timeout(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
},
|
||||
async detectPose() {
|
||||
const imageScaleFactor = 0.5;
|
||||
const flipHorizontal = false;
|
||||
const flipHorizontal = true;
|
||||
const outputStride = 16;
|
||||
this.pose = await this.net.estimateSinglePose(this.$refs.video, imageScaleFactor, flipHorizontal, outputStride)
|
||||
console.log(this.pose);
|
||||
utils.drawSkeleton(this.pose.keypoints, 0.3, this.ctx);
|
||||
//this.renderPose();
|
||||
// await this.timeout(1000/20);
|
||||
// return this.detectPose();
|
||||
requestAnimationFrame(() => {
|
||||
this.detectPose();
|
||||
})
|
||||
this.pose = await this.net.estimateSinglePose(
|
||||
this.$refs.video,
|
||||
imageScaleFactor,
|
||||
flipHorizontal,
|
||||
outputStride
|
||||
);
|
||||
|
||||
let playerId = this.getCookie("playerId");
|
||||
let roomId = this.$route.params.id;
|
||||
let send = {
|
||||
roomId: roomId,
|
||||
playerArr: [{ playerId: playerId, pose: this.pose }],
|
||||
};
|
||||
this.message = send;
|
||||
this.sendMessage();
|
||||
},
|
||||
updateColor() {
|
||||
this.setCookie("playerColour", this.colorSelect, 1);
|
||||
},
|
||||
updateUsername() {
|
||||
this.setCookie("playerUsername", this.username, 1);
|
||||
},
|
||||
setCookie(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
},
|
||||
draw() {
|
||||
utils.clearCanvas(this.ctx);
|
||||
if (this.playerArr) {
|
||||
utils.drawPlayers(this.playerArr, this.ctx);
|
||||
}
|
||||
|
||||
if (this.game != undefined) {
|
||||
utils.drawGame(this.game, this.ctx);
|
||||
}
|
||||
},
|
||||
async gameCycle() {
|
||||
this.draw();
|
||||
this.detectPose();
|
||||
await this.timeout(100);
|
||||
return this.gameCycle();
|
||||
//requestAnimationFrame(() => {
|
||||
// this.gameCycle();
|
||||
//})
|
||||
},
|
||||
getColour(colourSelect){
|
||||
const colours = {0: "red", 1: "green", 2:"blue", 3:"orange", 4:"purple", 5:"pink", 6:"black", 7:"brown", 8:"aqua"}
|
||||
return colours[colourSelect];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
58
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,23 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
Create Room: <button v-on:click="createRoom" style="border: solid">Create Room</button>
|
||||
<div class="cont">
|
||||
<main class="form-signin">
|
||||
|
||||
<center><img class="mb-4" :src="logo" alt="" width="30%" height="60%" style="width:60%; height 60%"></center>
|
||||
<h1 class="h3 mb-3 fw-normal text-center">Welcome to Chillspace!</h1>
|
||||
<label for="inputEmail" class="visually-hidden">Create a New Game:</label>
|
||||
<br>
|
||||
Join Room: <input type="text" v-model="room.id" style="border: solid"> <button v-on:click="joinRoom" style="border: solid">Join Room</button>
|
||||
<center><img :src="voleyball" @click="changeGame('0')" id="game-0" style="opacity: 0.25;"> <img :src="soccer" @click="changeGame('1')" id="game-1" style="opacity: 1;"> <img :src="dance" @click="changeGame('2')" id="game-2" style="opacity: 1;"></center>
|
||||
<button v-on:click="createRoom" class="w-100 btn btn-lg btn-primary" style="margin-top:15px;">Create Room</button>
|
||||
<br>
|
||||
<label for="input" class="visually-hidden" style="margin-top: 15px;">Join an Existing Room:</label>
|
||||
<input type="text" id="input" class="form-control" placeholder="Game ID" v-model="room.id">
|
||||
<div class="checkbox mb-3">
|
||||
</div>
|
||||
<button v-on:click="joinRoom" class="w-100 btn btn-lg btn-primary">Join Room</button>
|
||||
|
||||
<p class="mt-5 mb-3 text-muted"><a href="https://github.com/alex-alexiev" class="text-secondary">Alex Alexiev</a>, <a href="https://github.com/jslightham" class="text-secondary">Johnathon Slightham</a>, and <a href="https://github.com/lcarnegie" class="text-secondary">Luca Carnegie</a></p>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,23 +26,43 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
room: {},
|
||||
logo: require('@/assets/logo.jpg'),
|
||||
dance: require('@/assets/dance.jpg'),
|
||||
soccer: require('@/assets/soccer.jpg'),
|
||||
voleyball: require('@/assets/volleyball.jpg'),
|
||||
game: 0,
|
||||
};
|
||||
}, created() {
|
||||
this.setCookie("game", 0, 0);
|
||||
},
|
||||
methods: {
|
||||
createRoom() {
|
||||
this.axios.get("http://50.100.180.37:4000/rooms/add").then((res) => {
|
||||
this.axios.get("https://dance.cubehostingmc.com:4000/rooms/add").then((res) => {
|
||||
console.log("create");
|
||||
let id = res.data._id;
|
||||
let playerId = res.data.playerId;
|
||||
let playerColour = res.data.colour;
|
||||
this.setCookie("playerId", playerId, 1);
|
||||
this.setCookie("playerColour", playerColour, 1);
|
||||
this.setCookie("game", this.game, 1);
|
||||
console.log(res.data);
|
||||
this.$router.push({path: `game/${id}`});
|
||||
});
|
||||
}).catch(e => {console.log(e)});
|
||||
},
|
||||
joinRoom() {
|
||||
console.log(this.room);
|
||||
this.axios.post("http://50.100.180.37:4000/rooms/join", this.room).then((res) => {
|
||||
this.axios.post("https://dance.cubehostingmc.com:4000/rooms/join", this.room).then((res) => {
|
||||
if (res.data){
|
||||
console.log(res.data);
|
||||
let playerId = res.data._id;
|
||||
let playerColour = res.data.colour;
|
||||
this.setCookie("playerId", playerId, 1);
|
||||
this.setCookie("playerColour", playerColour, 1);
|
||||
console.log(res.data);
|
||||
this.$router.push({path: `game/${this.room.id}`});
|
||||
} else {
|
||||
alert("Please enter a valid room!");
|
||||
}
|
||||
});
|
||||
},
|
||||
setCookie(cname, cvalue, exdays) {
|
||||
@@ -36,9 +71,80 @@ export default {
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
},
|
||||
changeGame(game) {
|
||||
this.game = game;
|
||||
this.setCookie("game", game, 1);
|
||||
if (game == '0') {
|
||||
document.getElementById('game-0').style.opacity = "0.25";
|
||||
document.getElementById('game-1').style.opacity = "1";
|
||||
document.getElementById('game-2').style.opacity = "1";
|
||||
}
|
||||
if (game == '1') {
|
||||
document.getElementById('game-0').style.opacity = "1";
|
||||
document.getElementById('game-1').style.opacity = "0.25";
|
||||
document.getElementById('game-2').style.opacity = "1";
|
||||
}
|
||||
if (game == '2') {
|
||||
document.getElementById('game-0').style.opacity = "1";
|
||||
document.getElementById('game-1').style.opacity = "1";
|
||||
document.getElementById('game-2').style.opacity = "0.25";
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cont {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
126
src/components/MyCamera.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div id="my-camera">
|
||||
<div class="resultFrame">
|
||||
<video ref="video" autoplay></video>
|
||||
<canvas ref="canvas" :width="resultWidth" :height="resultHeight"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as posenet from "@tensorflow-models/posenet";
|
||||
import * as utils from "../utils";
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
data () {
|
||||
return {
|
||||
// store the promises of initialization
|
||||
streamPromise: null,
|
||||
// control the UI visibilities
|
||||
isVideoStreamReady: false,
|
||||
isModelReady: false,
|
||||
initFailMessage: '',
|
||||
pose: [],
|
||||
resultHeight: 0,
|
||||
resultWidth: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initWebcamStream () {
|
||||
// if the browser supports mediaDevices.getUserMedia API
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
return navigator.mediaDevices.getUserMedia({
|
||||
audio: false, // don't capture audio
|
||||
video: { facingMode: 'environment' } // use the rear camera if there is
|
||||
})
|
||||
.then(stream => {
|
||||
// set <video> source as the webcam input
|
||||
let video = this.$refs.video
|
||||
try {
|
||||
video.srcObject = stream
|
||||
} catch (error) {
|
||||
// support older browsers
|
||||
video.src = URL.createObjectURL(stream)
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
// when video is loaded
|
||||
video.onloadedmetadata = () => {
|
||||
// calculate the video ratio
|
||||
this.videoRatio = video.offsetHeight / video.offsetWidth
|
||||
// add event listener on resize to reset the <video> and <canvas> sizes
|
||||
window.addEventListener('resize', this.setResultSize)
|
||||
// set the initial size
|
||||
this.setResultSize()
|
||||
this.isVideoStreamReady = true
|
||||
console.log('webcam stream initialized')
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('failed to initialize webcam stream', error)
|
||||
throw (error)
|
||||
})
|
||||
} else {
|
||||
return Promise.reject(new Error('Your browser does not support mediaDevices.getUserMedia API'))
|
||||
}
|
||||
},
|
||||
setResultSize () {
|
||||
// get the current browser window size
|
||||
let clientWidth = document.documentElement.clientWidth
|
||||
// set max width as 600
|
||||
this.resultWidth = Math.min(600, clientWidth)
|
||||
// set the height according to the video ratio
|
||||
this.resultHeight = this.resultWidth * this.videoRatio
|
||||
// set <video> width and height
|
||||
/*
|
||||
Doesn't use vue binding :width and :height,
|
||||
because the initial value of resultWidth and resultHeight
|
||||
will affect the ratio got from the initWebcamStream()
|
||||
*/
|
||||
let video = this.$refs.video
|
||||
video.width = this.resultWidth
|
||||
video.height = this.resultHeight
|
||||
},
|
||||
timeout(ms){
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
},
|
||||
async detectPose() {
|
||||
const imageScaleFactor = 0.5;
|
||||
const flipHorizontal = true;
|
||||
const outputStride = 16;
|
||||
this.pose = await this.net.estimateSinglePose(this.$refs.video, imageScaleFactor, flipHorizontal, outputStride)
|
||||
console.log(this.pose);
|
||||
utils.drawSkeleton(this.pose.keypoints, 0.3, this.ctx);
|
||||
//this.renderPose();
|
||||
// await this.timeout(1000/20);
|
||||
// return this.detectPose();
|
||||
requestAnimationFrame(() => {
|
||||
this.detectPose();
|
||||
})
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.ctx = this.$refs.canvas.getContext("2d");
|
||||
this.net = await posenet.load();
|
||||
this.streamPromise = await this.initWebcamStream()
|
||||
this.detectPose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.resultFrame {
|
||||
display: grid;
|
||||
video {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
}
|
||||
canvas {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
48
src/components/MyCanvas.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>my canvas</h1>
|
||||
<canvas ref="canv"></canvas>
|
||||
<button @click="drawRect">Add Rect</button>
|
||||
<button @click="subWidth">-</button>
|
||||
<button @click="addWidth">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'my-canvas',
|
||||
data() {
|
||||
return {
|
||||
rectWidth: 200,
|
||||
width: 0,
|
||||
height: 0,
|
||||
pose: []
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.ctx = this.$refs.canv.getContext("2d");
|
||||
},
|
||||
|
||||
methods: {
|
||||
drawRect() {
|
||||
this.ctx.clearRect(0, 0, 400, 200);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.rect(20, 20, this.rectWidth, 100);
|
||||
this.ctx.stroke();
|
||||
},
|
||||
addWidth(){
|
||||
this.rectWidth += 10
|
||||
this.drawRect()
|
||||
},
|
||||
subWidth() {
|
||||
this.rectWidth -= 10
|
||||
this.drawRect()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,5 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import './assets/reset.css'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
|
||||
import VueRouter from 'vue-router';
|
||||
Vue.use(VueRouter);
|
||||
|
||||
145
src/utils.js
@@ -1,44 +1,147 @@
|
||||
import * as posenet from "@tensorflow-models/posenet";
|
||||
|
||||
const color = "aqua";
|
||||
const lineWidth = 2;
|
||||
const lineWidth = 6;
|
||||
const defaultConfidence = 0.5;
|
||||
|
||||
export const colors = { 0: "red", 1: "green", 2: "blue", 3: "orange", 4: "purple", 5: "pink", 6: "black", 7: "brown", 8: "aqua" }
|
||||
|
||||
|
||||
function toTuple({x, y}){
|
||||
export function drawGame(game, ctx) {
|
||||
if (game && game.name == "0") {
|
||||
try {
|
||||
game.data.objects.forEach(object => {
|
||||
let pos = object.position;
|
||||
let r = object.radius;
|
||||
drawPoint(ctx, pos.x, pos.y, r, "black");
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function toTuple({ x, y }) {
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function drawPoint(ctx, x, y, r, color){
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, r, 0, 2*Math.PI);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill;
|
||||
export function drawPlayers(players, ctx) {
|
||||
players.forEach(player => {
|
||||
drawPoint(ctx, player.head.x, player.head.y, 50, colors[player.colour]);
|
||||
ctx.font = "24px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillStyle = colors[player.colour];
|
||||
ctx.fillText(player.name, player.head.x, player.head.y - 60);
|
||||
player.universePairs.forEach(pair => {
|
||||
drawSegment([pair.x1, pair.y1], [pair.x2, pair.y2], colors[player.colour], 1, ctx);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function drawSegment([ax, ay], [bx, by], color, scale, ctx){
|
||||
export function assignAllUniversePairs(players, ctx) {
|
||||
try{
|
||||
let numPlayers = players.length;
|
||||
let width = ctx.canvas.width;
|
||||
let x_spacing = width / (numPlayers + 1);
|
||||
for (let i = 0; i < numPlayers; i++) {
|
||||
let x = (i + 1) * x_spacing;
|
||||
let y = ctx.canvas.height / 2;
|
||||
let com = getCenterOfMass(players[i].pose.keypoints);
|
||||
players[i].universePairs = keypointsToUniverse(players[i].pose.keypoints, com, [x, y])
|
||||
players[i].pose.keypoints.forEach(keypoint => {
|
||||
if (keypoint.part == "nose") {
|
||||
players[i].head = getUniversePoint(keypoint, com, [x, y])
|
||||
}
|
||||
})
|
||||
}
|
||||
}catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function keypointsToUniverse(keypoints, com, [x, y]) {
|
||||
let universePairs = []
|
||||
let minConfidence = 0.5;
|
||||
const adjacentKeyPoints = posenet.getAdjacentKeyPoints(keypoints, minConfidence);
|
||||
adjacentKeyPoints.forEach(keypoint => {
|
||||
universePairs.push(getUniversePair(keypoint[0], keypoint[1], com, [x, y]));
|
||||
})
|
||||
return universePairs;
|
||||
}
|
||||
|
||||
export function getUniversePair(keypoint1, keypoint2, com, [x, y]) {
|
||||
let p1 = getUniversePoint(keypoint1, com, [x, y]);
|
||||
let p2 = getUniversePoint(keypoint2, com, [x, y]);
|
||||
return { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }
|
||||
}
|
||||
|
||||
export function getUniversePoint(keypoint, com, [x, y]) {
|
||||
let xNew = keypoint.position.x - com[0];
|
||||
let yNew = keypoint.position.y - com[1];
|
||||
xNew = -xNew;// flip horizontally
|
||||
xNew += x;
|
||||
yNew += y;
|
||||
return { x: xNew, y: yNew }
|
||||
}
|
||||
|
||||
export function getCenterOfMass(keypoints) {
|
||||
let x_sum = 0, y_sum = 0, num = 0;
|
||||
keypoints.forEach(keypoint => {
|
||||
if (isConfident(keypoint)) {
|
||||
x_sum += keypoint.position.x;
|
||||
y_sum += keypoint.position.y;
|
||||
num++;
|
||||
}
|
||||
})
|
||||
let com = [(x_sum / num), (y_sum / num)];
|
||||
return com;
|
||||
}
|
||||
|
||||
export function isConfident(keypoint) {
|
||||
return keypoint.score > defaultConfidence;
|
||||
}
|
||||
|
||||
|
||||
export function clearCanvas(ctx) {
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
|
||||
}
|
||||
|
||||
export function drawPoint(ctx, x, y, r, color) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax*scale, ay*scale);
|
||||
ctx.lineTo(bx*scale, by*scale);
|
||||
ctx.arc(x, y, r, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
export function drawSegment([ax, ay], [bx, by], color, scale, ctx) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax * scale, ay * scale);
|
||||
ctx.lineTo(bx * scale, by * scale);
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.strokeStyle = color;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
export function drawSkeleton(keypoints, minConfidence, ctx, scale=1){
|
||||
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
|
||||
export function drawRawSkeleton(keypoints, minConfidence, ctx, color, scale = 1) {
|
||||
const adjacentKeyPoints = posenet.getAdjacentKeyPoints(keypoints, minConfidence);
|
||||
adjacentKeyPoints.forEach(keypoints => {
|
||||
drawSegment(toTuple(keypoints[0].position), toTuple(keypoints[1].position), color, scale, ctx);
|
||||
adjacentKeyPoints.forEach(keypoint => {
|
||||
drawSegment(toTuple(keypoint[0].position), toTuple(keypoint[1].position), color, scale, ctx);
|
||||
})
|
||||
}
|
||||
|
||||
export function drawKeypoints(keypoints, minConfidence, ctx, scale = 1){
|
||||
keypoints.forEach(keypoints => {
|
||||
if (keypoints.score < minConfidence){
|
||||
export function drawKeypoints(keypoints, minConfidence, ctx, color, scale = 1) {
|
||||
keypoints.forEach(keypoint => {
|
||||
if (keypoint.score < minConfidence) {
|
||||
return;
|
||||
}
|
||||
const {x, y} = keypoints.position;
|
||||
drawPoint(ctx, x*scale, y*scale, 3, color);
|
||||
const { x, y } = keypoint.position;
|
||||
if (keypoint.part == "nose") {
|
||||
drawPoint(ctx, x * scale, y * scale, 40, color);
|
||||
} else {
|
||||
|
||||
drawPoint(ctx, x * scale, y * scale, 3, color);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
purge: [],
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
9
vue.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
open: process.platform === 'darwin',
|
||||
host: '0.0.0.0',
|
||||
port: 8080, // CHANGE YOUR PORT HERE!
|
||||
https: true,
|
||||
hotOnly: false,
|
||||
},
|
||||
}
|
||||