This commit is contained in:
Johnathon Slightham
2021-09-07 12:53:34 -04:00
parent 57b9827e64
commit 71017cc5b6
34 changed files with 27214 additions and 2626 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/assets/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

0
src/assets/reset.css Normal file
View File

BIN
src/assets/soccer.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/volleyball.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -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 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();
})
}
const imageScaleFactor = 0.5;
const flipHorizontal = true;
const outputStride = 16;
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>

View 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>

View File

@@ -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) => {
let playerId = res.data._id;
this.setCookie("playerId", playerId, 1);
this.$router.push({path: `game/${this.room.id}`});
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
View 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>

View 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>

View File

@@ -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);

View File

@@ -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){
return;
export function drawKeypoints(keypoints, minConfidence, ctx, color, scale = 1) {
keypoints.forEach(keypoint => {
if (keypoint.score < minConfidence) {
return;
}
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);
}
const {x, y} = keypoints.position;
drawPoint(ctx, x*scale, y*scale, 3, color);
})
}