diff --git a/index.html b/index.html index 781bab7..f1298b5 100644 --- a/index.html +++ b/index.html @@ -8,15 +8,14 @@ -
-

Star Gazer

- - -
- - +
+
Star Gazer 2000
+
Start Game
+
+ - + \ No newline at end of file diff --git a/resources/css/default.css b/resources/css/default.css index e69de29..7e50b95 100644 --- a/resources/css/default.css +++ b/resources/css/default.css @@ -0,0 +1,63 @@ +/* colors: https://lospec.com/palette-list/spf-80 */ + +@font-face { + font-family: "ShareTech"; + src: url("../fonts/ShareTechMono/ShareTechMono-Regular.ttf"); +} + +.hidden { + display: none; +} + +body { + margin: 0; + padding: 0; + font-family: "ShareTech"; +} + +#startScreen { + width: 100vw; + height: 100vh; + background: rgb(0, 0, 0); +} + +#startTitle { + position: relative; + width: 60vw; + height: 10vh; + top: 20vh; + margin: 0 auto; + text-align: center; + line-height: 10vh; + font-size: 10vh; + color: #FFF; + text-shadow: 5px 5px 5px #611894; +} + +#startButton { + position: relative; + width: 30vw; + height: 10vh; + top: 30vh; + margin: 0 auto; + border-radius: 5px; + border-width: 5px; + border-style: solid; + background-color: #1b744a; + border-color: #aaffd8; + text-align: center; + line-height: 10vh; + font-size: 5vh; + color: #FFF; +} + +#startButton:hover { + cursor: pointer; + background-color: #aaffd8; + border-color: #1b744a; + color: #1b744a; +} + +canvas { + cursor: none; +} \ No newline at end of file diff --git a/resources/fonts/ShareTechMono/OFL.txt b/resources/fonts/ShareTechMono/OFL.txt new file mode 100644 index 0000000..2ff09c1 --- /dev/null +++ b/resources/fonts/ShareTechMono/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2012, Carrois Type Design, Ralph du Carrois (post@carrois.com www.carrois.com), with Reserved Font Name 'Share' + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/resources/fonts/ShareTechMono/ShareTechMono-Regular.ttf b/resources/fonts/ShareTechMono/ShareTechMono-Regular.ttf new file mode 100644 index 0000000..c8e530f Binary files /dev/null and b/resources/fonts/ShareTechMono/ShareTechMono-Regular.ttf differ diff --git a/resources/js/Config.js b/resources/js/Config.js new file mode 100644 index 0000000..c084139 --- /dev/null +++ b/resources/js/Config.js @@ -0,0 +1,13 @@ +var Config = { + GAZE_SERVER_URL: "ws://localhost:8001/gaze", + TARGET_FPS: 120, + SHOW_FPS: true, + USE_MOUSE_INPUT_AS_FAKE_GAZE_DATA: true, + USE_LOGGER: true, + SCREEN_WIDTH: screen.width, + SCREEN_HEIGHT: screen.height, +}; + +Object.freeze(Config); + +export default Config; \ No newline at end of file diff --git a/resources/js/game/GameManager.js b/resources/js/game/GameManager.js index b53a6c9..45ba562 100644 --- a/resources/js/game/GameManager.js +++ b/resources/js/game/GameManager.js @@ -1,13 +1,13 @@ -import Logger from "../utils/Logger.js"; - -const BACKGROUND_COLOR = "rgba(30,30,30,0.5)", - DEFAULT_GAZE_POINT_RADIUS = 10, +const BACKGROUND_COLOR = "#333", + DEFAULT_GAZE_POINT_RADIUS = 15, DEFAULT_GAZE_POINT_COLOR = "#ff007b", - FPS_COLOR = "#ffbb00", + FPS_FONT = "16px ShareTech", + FPS_COLOR = "#ffffff", MAX_GAZE_POINT_AGE = 500; var lastUpdate, - lastDelta; + lastDelta, + fpsBuffer = []; class GameManger { @@ -17,7 +17,7 @@ class GameManger { setCanvas(canvas) { this.canvas = canvas; - this.context = this.canvas.getContext("2d"); + this.context = this.canvas.getContext("2d", { alpha: false }); this.canvas.style.backgroundColor = BACKGROUND_COLOR; } @@ -87,12 +87,13 @@ class GameManger { let now = Date.now(); this.context.fillStyle = DEFAULT_GAZE_POINT_COLOR; for (let item of this.gazePoints) { + let relativeAge = 1 - ((now - item[1].createdAt) / + MAX_GAZE_POINT_AGE); this.context.save(); - this.context.globalAlpha = 1 - (now - item[1].createdAt) / - MAX_GAZE_POINT_AGE; + this.context.globalAlpha = relativeAge; this.context.beginPath(); this.context.ellipse(item[1].targetX, item[1].targetY, - DEFAULT_GAZE_POINT_RADIUS, DEFAULT_GAZE_POINT_RADIUS, Math.PI / 4, + relativeAge * DEFAULT_GAZE_POINT_RADIUS, relativeAge * DEFAULT_GAZE_POINT_RADIUS, Math.PI / 4, 0, 2 * Math.PI); this.context.fill(); this.context.closePath(); @@ -101,11 +102,17 @@ class GameManger { } drawFrameRate() { - let fps = parseInt(1000 / lastDelta); + let fps = parseInt(1000 / lastDelta), + averageFps; + fpsBuffer.push(fps); + if(fpsBuffer.length === this.fps) { + averageFps = parseInt(fpsBuffer.reduce((a,b) => a + b, 0) / fpsBuffer.length); + fpsBuffer.shift(); + } this.context.beginPath(); - this.context.font = "20px Arial"; + this.context.font = FPS_FONT; this.context.fillStyle = FPS_COLOR; - this.context.fillText(`FPS: ${fps}`, 30, 30); + this.context.fillText(`FPS: ${averageFps}`, 30, 30); this.context.closePath(); } diff --git a/resources/js/game/StarGazer.js b/resources/js/game/StarGazer.js index fbe84fa..c55e384 100644 --- a/resources/js/game/StarGazer.js +++ b/resources/js/game/StarGazer.js @@ -1,30 +1,31 @@ import Logger from "../utils/Logger.js"; import GameManager from "./GameManager.js"; -var canvas, gm; +var canvas, gm, provider; -class StarGazer { - - init(config) { - Logger.log("Starting StarGazer game"); - canvas = config.canvas; - gm = new GameManager(); - gm.setCanvas(canvas); - gm.setFrameRate(config.fps); - if (config.showFPS === true) { - gm.showFPS(); - } - gm.setSize(config.width, config.height); - gm.start(); +function init(config) { + Logger.log("Starting StarGazer game"); + canvas = config.canvas; + gm = new GameManager(); + gm.setCanvas(canvas); + gm.setFrameRate(config.fps); + if (config.showFPS === true) { + gm.showFPS(); } + gm.setSize(config.width, config.height); + gm.start(); + provider = config.gazeDataProvider; + provider.addEventListener("dataavailable", onGazeUpdate); +} - onGazeUpdate(gazePoint) { - gazePoint.linkTo(canvas); - if (gazePoint.hasLink) { - gm.addGazePoint(gazePoint); - } +function onGazeUpdate(event) { + let gazePoint = event.data; + gazePoint.linkTo(canvas); + if (gazePoint.hasLink) { + gm.addGazePoint(gazePoint); } - } -export default new StarGazer(); \ No newline at end of file +export default { + init: init, +}; \ No newline at end of file diff --git a/resources/js/gaze/FakeGazeDataProvider.js b/resources/js/gaze/FakeGazeDataProvider.js new file mode 100644 index 0000000..cbdd1a2 --- /dev/null +++ b/resources/js/gaze/FakeGazeDataProvider.js @@ -0,0 +1,23 @@ +import GazeDataProvider from "./GazeDataProvider.js"; +import Event from "../utils/Event.js"; +import GazePoint from "./GazePoint.js"; + +class FakeGazeDataProvider extends GazeDataProvider { + + constructor() { + super(); + } + + start() { + window.addEventListener("mousemove", this.onMouseDataAvailable.bind(this)); + } + + onMouseDataAvailable(event) { + let gazeEvent = new Event("dataavailable", new GazePoint(event.screenX, + event.screenY)); + this.notifyAll(gazeEvent); + } + +} + +export default FakeGazeDataProvider; \ No newline at end of file diff --git a/resources/js/gaze/GazeDataProvider.js b/resources/js/gaze/GazeDataProvider.js new file mode 100644 index 0000000..32a24d3 --- /dev/null +++ b/resources/js/gaze/GazeDataProvider.js @@ -0,0 +1,40 @@ +/* global GazeClient */ + +import Observable from "../utils/Observable.js"; +import Event from "../utils/Event.js"; +import Logger from "../utils/Logger.js"; +import GazePoint from "../gaze/GazePoint.js"; + +class GazeDataProvider extends Observable { + + constructor() { + super(); + } + + start(url) { + this.gclient = new GazeClient(); + this.gclient.connect(url); + this.gclient.addEventListener("connectionopened", this.onConnected.bind(this)); + this.gclient.addEventListener("dataavailable", this.onGazeDataAvailable.bind( + this)); + this.gclient.addEventListener("connectionclosed", this.onDisconnected.bind(this)); + } + + onConnected(event) { + Logger.log(event); + } + + onGazeDataAvailable(event) { + let eyeX = (event.data.leftEyeX + event.data.rightEyeX) / 2, + eyeY = (event.data.leftEyeY + event.data.rightEyeY) / 2, + gazeEvent = new Event("dataavailable", new GazePoint(eyeX, eyeY)); + this.notifyAll(gazeEvent); + } + + onDisconnected(event) { + Logger.log(event); + } + +} + +export default GazeDataProvider; \ No newline at end of file diff --git a/resources/js/game/GazePoint.js b/resources/js/gaze/GazePoint.js similarity index 72% rename from resources/js/game/GazePoint.js rename to resources/js/gaze/GazePoint.js index ec5a239..38535c4 100644 --- a/resources/js/game/GazePoint.js +++ b/resources/js/gaze/GazePoint.js @@ -1,27 +1,29 @@ class GazePoint { - constructor(screenX, screenY, createdAt, id) { + constructor(screenX, screenY) { this.screenX = screenX; this.screenY = screenY; this.createdAt = Date.now(); - this.id = id || this.createdAt; + this.id = this.createdAt; } linkTo(node) { let bb = node.getBoundingClientRect(), - elementLeft = window.pageXOffset + bb.left, - elementTop = window.pageYOffset + bb.top, - elementRight = window.pageXOffset + bb.right, - elementBottom = window.pageYOffset + bb.bottom, + // Difference between height of browser window (including menus and decoration) and actual viewport height + yOffSet = window.outerHeight - window.innerHeight, + elementLeft = window.screenX + bb.left, + elementTop = window.screenY + bb.top + yOffSet, + elementRight = window.screenX + bb.right, + elementBottom = window.screenY + bb.bottom + yOffSet, coordinates; - if (this.screenX >= elementLeft && this.screenX <= elementRight && this - .screenY >= - elementTop && this.screenY <= elementBottom) { - this.hasLink = true; - this.link = node; - this.targetX = this.screenX - elementLeft; - this.targetY = this.screenY - elementTop; - } + if (this.screenX >= elementLeft && this.screenX <= elementRight && this + .screenY >= + elementTop && this.screenY <= elementBottom) { + this.hasLink = true; + this.link = node; + this.targetX = this.screenX - elementLeft; + this.targetY = this.screenY - elementTop; + } return coordinates; } diff --git a/resources/js/index.js b/resources/js/index.js index 5fcdf7a..5eebe76 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -1,48 +1,47 @@ -/* global GazeClient */ - +import Config from "./Config.js"; import Logger from "./utils/Logger.js"; import StarGazer from "./game/StarGazer.js"; -import GazePoint from "./game/GazePoint.js"; +import FakeGazeDataProvider from "./gaze/FakeGazeDataProvider.js"; +import GazeDataProvider from "./gaze/GazeDataProvider.js"; -var gclient = new GazeClient(); +var canvas = document.querySelector("canvas"), + starScreen = document.querySelector("#startScreen"); function init() { - Logger.enable(); - initGazeClient(); - initStarGazer(); -} - -function initGazeClient() { - gclient.connect("ws://localhost:8001/gaze"); - gclient.addEventListener("connectionopened", onConnected); - gclient.addEventListener("dataavailable", onGazeDataAvailable); - gclient.addEventListener("connectionclosed", onDisconnected); + if (Config.USE_LOGGER === true) { + Logger.enable(); + } + document.querySelector("#startButton").addEventListener("click", + prepareGame); } -function initStarGazer() { +function prepareGame() { + let dataProvider = getDataProvider(); StarGazer.init({ - canvas: document.querySelector("canvas"), - fps: 60, - showFPS: true, - width: 800, - height: 800, + canvas: canvas, + fps: Config.TARGET_FPS, + showFPS: Config.SHOW_FPS, + width: Config.SCREEN_WIDTH, + height: Config.SCREEN_HEIGHT, + gazeDataProvider: dataProvider, }); + canvas.requestFullscreen().then(startGame); } -function onConnected(event) { - console.log(event); -} - -function onGazeDataAvailable(event) { - let eyeX = (event.data.leftEyeX + event.data.rightEyeX) / 2, - eyeY = (event.data.leftEyeY + event.data.rightEyeY) / 2, - createdAt = event.data.trackerTimeStamp, - gazePoint = new GazePoint(eyeX, eyeY, createdAt); - StarGazer.onGazeUpdate(gazePoint); +function startGame() { + starScreen.classList.add("hidden"); + canvas.classList.remove("hidden"); } -function onDisconnected(event) { - console.log( event); +function getDataProvider() { + let provider; + if (Config.USE_MOUSE_INPUT_AS_FAKE_GAZE_DATA === true) { + provider = new FakeGazeDataProvider(); + } else { + provider = new GazeDataProvider(); + } + provider.start(Config.GAZE_SERVER_URL); + return provider; } init(); \ No newline at end of file diff --git a/resources/js/utils/Events.js b/resources/js/utils/Event.js similarity index 100% rename from resources/js/utils/Events.js rename to resources/js/utils/Event.js