{"id":1100,"date":"2013-07-22T11:53:40","date_gmt":"2013-07-22T11:53:40","guid":{"rendered":"http:\/\/www.osd.net\/blog\/?p=1100"},"modified":"2013-07-22T11:53:40","modified_gmt":"2013-07-22T11:53:40","slug":"3d-board-game-in-a-browser-using-webgl-and-three-js-part-3","status":"publish","type":"post","link":"https:\/\/www.osd.net\/blog\/web-development\/3d-board-game-in-a-browser-using-webgl-and-three-js-part-3\/","title":{"rendered":"3D board game in a browser using WebGL and Three.js, part 3"},"content":{"rendered":"<p>Continuing from <a title=\"3D board game in a browser using WebGL and Three.js, part 2\" href=\"https:\/\/www.osd.net\/blog\/web-development\/3d-board-game-in-a-browser-using-webgl-and-three-js-part-2\/\">part 2<\/a> this final article will show you how to make the pieces move by drag and drop and adding some game logic.<\/p>\n<p><a href=\"https:\/\/www.osd.net\/blog\/wp-content\/uploads\/2013\/07\/webgl_3d_checkers_board_pieces_dragged.jpg\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-1117\" title=\"WebGL 3d Checkers Board - pieces dragged\" alt=\"WebGL 3d Checkers Board - pieces dragged\" src=\"https:\/\/www.osd.net\/blog\/wp-content\/uploads\/2013\/07\/webgl_3d_checkers_board_pieces_dragged.jpg\" width=\"599\" height=\"420\" srcset=\"https:\/\/www.osd.net\/blog\/wp-content\/uploads\/2013\/07\/webgl_3d_checkers_board_pieces_dragged.jpg 599w, https:\/\/www.osd.net\/blog\/wp-content\/uploads\/2013\/07\/webgl_3d_checkers_board_pieces_dragged-300x210.jpg 300w\" sizes=\"(max-width: 599px) 100vw, 599px\" \/><\/a><\/p>\n<p><strong>Note:<\/strong> Unless specified otherwise, the file you need to edit will be <em>BoardController.js<\/em>.<\/p>\n<h2>Restrict camera rotation<\/h2>\n<p>The first thing we need to do is to stop the camera from moving if the left mouse button is pressed on the board area. We&#8217;ll have to create a few functions to accomplish that. Let&#8217; start by creating an <em>initListeners<\/em> function in which we&#8217;ll listen for mouse events:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction initObjects(callback) { ... }\r\n\r\nfunction initListeners() {\r\n    var domElement = renderer.domElement;\r\n\r\n    domElement.addEventListener('mousedown', onMouseDown, false);\r\n    domElement.addEventListener('mouseup', onMouseUp, false);\r\n}\r\n\r\n<\/pre>\n<p>Call the new function from <em>drawBoard<\/em>, after <em>initObjects<\/em>. After\u00a0<em>onAnimationFrame<\/em> function definition add the mouse event handlers:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\r\nfunction onMouseDown(event) {\r\n    var mouse3D = getMouse3D(event);\r\n\r\n    if (isMouseOnBoard(mouse3D)) {\r\n        cameraController.userRotate = false;\r\n    }\r\n}\r\n\r\nfunction onMouseUp(event) {\r\n    cameraController.userRotate = true;\r\n}\r\n\r\n<\/pre>\n<p>On line 2 the mouse 2D coordinates are converted to a 3D position. On line 4 we check if the mouse was pressed on the board and if true, the camera rotation is disabled on line 5. On mouse up the camera rotation will be re-enabled. To calculate the 3D mouse position we&#8217;ll need a <a title=\"THREE.Projector\" href=\"http:\/\/threejs.org\/docs\/58\/#Reference\/Core\/Projector\">THREE.Projector<\/a> instance, so let&#8217;s create one in <em>initEngine<\/em> function:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n...\r\nvar renderer;\r\nvar projector;\r\n...\r\n\r\nfunction initEngine() {\r\n    ...\r\n    renderer.setSize(viewWidth, viewHeight);\r\n\r\n    projector = new THREE.Projector();\r\n    ...\r\n}\r\n<\/pre>\n<p>Now we&#8217;re ready to define our <strong>getMouse3D<\/strong> function after <em>boardToWorld<\/em>:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction getMouse3D(mouseEvent) {\r\n    var x, y;\r\n    \/\/\r\n    if (mouseEvent.offsetX !== undefined) {\r\n        x = mouseEvent.offsetX;\r\n        y = mouseEvent.offsetY;\r\n    } else {\r\n        x = mouseEvent.layerX;\r\n        y = mouseEvent.layerY;\r\n    }\r\n\r\n    var pos = new THREE.Vector3(0, 0, 0);\r\n    var pMouse = new THREE.Vector3(\r\n        (x \/ renderer.domElement.width) * 2 - 1,\r\n       -(y \/ renderer.domElement.height) * 2 + 1,\r\n       1\r\n    );\r\n    \/\/\r\n    projector.unprojectVector(pMouse, camera);\r\n\r\n    var cam = camera.position;\r\n    var m = pMouse.y \/ ( pMouse.y - cam.y );\r\n\r\n    pos.x = pMouse.x + ( cam.x - pMouse.x ) * m;\r\n    pos.z = pMouse.z + ( cam.z - pMouse.z ) * m;\r\n\r\n    return pos;\r\n}\r\n<\/pre>\n<p>What I can say about the <em>getMouse3D<\/em> function is that the value for Y axis will always be 0 since we just need to know where the mouse was clicked in the XZ plane space. You can just take the algorithm that translates 2D position to a 3D one as it is, it&#8217;s not my creation. The <strong>isMouseOnBoard<\/strong> function is defined after <em>getMouse3D<\/em> like this:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction isMouseOnBoard(pos) {\r\n    if (pos.x &gt;= 0 &amp;&amp; pos.x &lt;= squareSize * 8 &amp;&amp;\r\n        pos.z &gt;= 0 &amp;&amp; pos.z &lt;= squareSize * 8) {\r\n        return true;\r\n    } else {\r\n        return false;\r\n    }\r\n}\r\n<\/pre>\n<p>We just check if the mouse event happened inside the board&#8217;s squares area. If you test in the browser now you&#8217;ll see that you can only manipulate the camera by dragging outside the board (or on its margins).<\/p>\n<h2>Dragging the pieces<\/h2>\n<p>To drag the pieces around we need to check if the mouse was pressed on a piece in the mouse down event handler. If the mouse was on a piece (actually on a piece square) we need to select it and to start listening for mouse move event. Let&#8217;s modify the <em>onMouseDown<\/em> function:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n...\r\nif (isMouseOnBoard(mouse3D)) {\r\n    if (isPieceOnMousePosition(mouse3D)) {\r\n        selectPiece(mouse3D);\r\n        renderer.domElement.addEventListener('mousemove', onMouseMove, false);\r\n    }\r\n\r\n    cameraController.userRotate = false;\r\n}\r\n...\r\n<\/pre>\n<p>Add the <strong>isPieceOnMousePosition<\/strong> function after <em>isMouseOnBoard<\/em>:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction isPieceOnMousePosition(pos) {\r\n    var boardPos = worldToBoard(pos);\r\n\r\n    if (boardPos &amp;&amp; board[ boardPos[0] ][ boardPos[1] ] !== 0) {\r\n        return true;\r\n    }\r\n\r\n    return false;\r\n}\r\n<\/pre>\n<p>First the 3D position must be converted to the internal board position on line 2. On the 4th line we check if there&#8217;s an actual piece in the position we found. Define the <strong>worldToBoard<\/strong> function after <em>boardToWorld<\/em> like this:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction worldToBoard(pos) {\r\n    var i = 8 - Math.ceil((squareSize * 8 - pos.z) \/ squareSize);\r\n    var j = Math.ceil(pos.x \/ squareSize) - 1;\r\n\r\n    if (i &gt; 7 || i &lt; 0 || j &gt; 7 || j &lt; 0 || isNaN(i) || isNaN(j)) {\r\n        return false;\r\n    }\r\n\r\n    return [i, j];\r\n}\r\n<\/pre>\n<p>Now we need to define the <strong>selectPiece<\/strong> function after <em>isPieceOnMousePosition<\/em>. A new property also needs to be declared:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n...\r\nvar board = [...];\r\n\r\nvar selectedPiece = null;\r\n...\r\nfunction isPieceOnMousePosition(pos) {...}\r\n\r\nfunction selectPiece(pos) {\r\n    var boardPos = worldToBoard(pos);\r\n\r\n    \/\/ check for piece presence\r\n    if (board[ boardPos[0] ][ boardPos[1] ] === 0) {\r\n        selectedPiece = null;\r\n        return false;\r\n    }\r\n\r\n    selectedPiece = {};\r\n    selectedPiece.boardPos = boardPos;\r\n    selectedPiece.obj = board[ boardPos[0] ][ boardPos[1] ];\r\n    selectedPiece.origPos = selectedPiece.obj.position.clone();\r\n\r\n    return true;\r\n}\r\n<\/pre>\n<p>Besides storing the actual piece object, we need to store its position also to be able to make a move or to put it back where it was on illegal move.<\/p>\n<p>Define the <em>onMouseMove<\/em> function like this:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\r\nfunction onMouseMove(event) {\r\n    var mouse3D = getMouse3D(event);\r\n\r\n    \/\/ drag selected piece\r\n    if (selectedPiece) {\r\n        selectedPiece.obj.position.x = mouse3D.x;\r\n        selectedPiece.obj.position.z = mouse3D.z;\r\n\r\n        \/\/ lift piece\r\n        selectedPiece.obj.children[0].position.y = 0.75;\r\n    }\r\n}\r\n\r\n<\/pre>\n<p>If you try to move a piece now you&#8217;ll notice that it&#8217;s sticking to the mouse cursor even after mouse up release. Let&#8217;s fix that <em>onMouseUp<\/em>:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction onMouseUp(event) {\r\n    renderer.domElement.removeEventListener('mousemove', onMouseMove, false);\r\n\r\n    var mouse3D = getMouse3D(event);\r\n\r\n    if (isMouseOnBoard(mouse3D) &amp;&amp; selectedPiece) {\r\n        var toBoardPos = worldToBoard(mouse3D);\r\n\r\n        if (toBoardPos[0] === selectedPiece.boardPos[0] &amp;&amp; toBoardPos[1] === selectedPiece.boardPos[1]) {\r\n            deselectPiece();\r\n        } else {\r\n            instance.movePiece(selectedPiece.boardPos, toBoardPos);\r\n            selectedPiece = null;\r\n        }\r\n    } else {\r\n        deselectPiece();\r\n    }\r\n\r\n    cameraController.userRotate = true;\r\n}\r\n<\/pre>\n<p>If an illegal move is made the <strong>deselectPiece<\/strong> function will reset the piece position, otherwise the <strong>movePiece<\/strong> will place the piece to a new position. Here&#8217;s how <em>deselectPiece<\/em> looks like:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction selectPiece(pos) {...}\r\n\r\nfunction deselectPiece() {\r\n    if (!selectedPiece) {\r\n        return;\r\n    }\r\n\r\n    selectedPiece.obj.position = selectedPiece.origPos;\r\n    selectedPiece.obj.children[0].position.y = 0;\r\n\r\n    selectedPiece = null;\r\n}\r\n<\/pre>\n<p>The <em>movePiece<\/em> method we want it to be public, so we&#8217;ll have to store the instance of the <em>BoardController<\/em> to be accessible from inside the mouse event handlers:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n...\r\noptions = options || {};\r\n\r\nvar instance = this;\r\n...\r\n\r\nthis.addPiece = function (piece) {...};\r\n\r\nthis.movePiece = function (from, to) {\r\n    var piece = board[ from[0] ][ from[1] ];\r\n    var capturedPiece = board[ to[0] ][ to[1] ];\r\n    var toWorldPos = boardToWorld(to);\r\n\r\n    \/\/ update internal board\r\n    board[ from[0] ][ from[1] ] = 0;\r\n    delete board[ to[0] ][ to[1] ];\r\n    board[ to[0] ][ to[1] ] = piece;\r\n\r\n    \/\/ capture piece\r\n    if (capturedPiece !== 0) {\r\n        scene.remove(capturedPiece);\r\n    }\r\n\r\n    \/\/ move piece\r\n    piece.position.x = toWorldPos.x;\r\n    piece.position.z = toWorldPos.z;\r\n\r\n    piece.children[0].position.y = 0;\r\n};\r\n<\/pre>\n<p>While moving the piece, we also remove the piece from the destination position if necessary. Now you can try moving the pieces in the browser. You should be able to move them freely on every square you want and if you drop them outside the board area the pieces will be placed back to their initial position.<\/p>\n<h2>Adding game logic<\/h2>\n<p>The checkers game logic will reside in <em>Game.js<\/em>, so we&#8217;ll have to make the <em>BoardController<\/em> notify the <em>Game<\/em> when a piece needs to be\/or have dropped. We&#8217;ll make that happen by allowing some callbacks to be registered when the <em>BoardController<\/em> is instantiated:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n..\r\nvar board = [...];\r\nvar selectedPiece = null;\r\n\r\nvar callbacks = options.callbacks || {};\r\n...\r\n<\/pre>\n<p>Now let&#8217;s make use of those callbacks in onMouseUp function:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction onMouseUp(event) {\r\n    renderer.domElement.removeEventListener('mousemove', onMouseMove, false);\r\n\r\n    var mouse3D = getMouse3D(event);\r\n\r\n    if (isMouseOnBoard(mouse3D) &amp;&amp; selectedPiece) {\r\n        var toBoardPos = worldToBoard(mouse3D);\r\n\r\n        if (toBoardPos[0] === selectedPiece.boardPos[0] &amp;&amp; toBoardPos[1] === selectedPiece.boardPos[1]) {\r\n            deselectPiece();\r\n        } else {\r\n            if (callbacks.pieceCanDrop &amp;&amp; callbacks.pieceCanDrop(selectedPiece.boardPos, toBoardPos, selectedPiece.obj.color)) {\r\n                instance.movePiece(selectedPiece.boardPos, toBoardPos);\r\n\r\n                if (callbacks.pieceDropped) {\r\n                    callbacks.pieceDropped(selectedPiece.boardPos, toBoardPos, selectedPiece.obj.color);\r\n                }\r\n\r\n                selectedPiece = null;\r\n            } else {\r\n                deselectPiece();\r\n            }\r\n        }\r\n    } else {\r\n        deselectPiece();\r\n    }\r\n\r\n    cameraController.userRotate = true;\r\n}\r\n<\/pre>\n<p>Instead of just moving the piece, on line 12 the <em>pieceCanDrop<\/em> callback is called to check if the piece can be dropped on the requested square. If returns true the piece is moved and the\u00a0<em>pieceDropped<\/em> callback is called to allow the <em>Game<\/em> to remove the captured pieces.<\/p>\n<p>Now let&#8217;s add some game logic into <strong>Game.js<\/strong>. We&#8217;ll start by creating a variable to keep track of which side&#8217;s turn is and by modifying the <em>init<\/em> function to pass some callbacks to <em>BoardController<\/em> instance:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n...\r\nvar board = [...];\r\n\r\nvar colorTurn = CHECKERS.WHITE;\r\n\r\nfunction init() {\r\n    boardController = new CHECKERS.BoardController({\r\n        containerEl: options.containerEl,\r\n        assetsUrl: options.assetsUrl,\r\n        callbacks: {\r\n            pieceCanDrop: isMoveLegal,\r\n            pieceDropped: pieceMoved\r\n        }\r\n    });\r\n\r\n    boardController.drawBoard(onBoardReady);\r\n}\r\n<\/pre>\n<p>Here&#8217;s how the <strong>isMoveLegal<\/strong> function looks like:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction onBoardReady() {...}\r\n\r\nfunction isMoveLegal(from, to, color) {\r\n    if (color !== colorTurn) {\r\n        return false;\r\n    }\r\n\r\n    var fromRow = from[0];\r\n    var fromCol = from[1];\r\n    var toRow = to[0];\r\n    var toCol = to[1];\r\n\r\n    if (board[toRow][toCol] !== 0) { \/\/ a piece can't be dropped on an existing piece\r\n        return false;\r\n    }\r\n\r\n    if (color === CHECKERS.BLACK) {\r\n        \/\/ checks for one square move in left or right direction\r\n        if (toRow === fromRow + 1 &amp;&amp; (toCol === fromCol - 1 || toCol === fromCol + 1)) {\r\n            return true;\r\n        }\r\n\r\n        \/\/ checks for 2 squares move (jumping over a piece)\r\n        if (toRow === fromRow + 2) {\r\n            \/\/ left direction\r\n            if (toCol === fromCol - 2 &amp;&amp; board[fromRow + 1][fromCol - 1] !== 0 &amp;&amp; board[fromRow + 1][fromCol - 1].color != color) {\r\n                return true;\r\n            }\r\n\r\n            \/\/ right direction\r\n            if (toCol === fromCol + 2 &amp;&amp; board[fromRow + 1][fromCol + 1] !== 0 &amp;&amp; board[fromRow + 1][fromCol + 1].color != color) {\r\n                return true;\r\n            }\r\n        }\r\n    } else if (color === CHECKERS.WHITE) {\r\n        \/\/ checks for one square move in left or right direction\r\n        if (toRow === fromRow - 1 &amp;&amp; (toCol === fromCol - 1 || toCol === fromCol + 1)) {\r\n            return true;\r\n        }\r\n\r\n        \/\/ checks for 2 squares move (jumping over a piece)\r\n        if (toRow === fromRow - 2) {\r\n            \/\/ left direction\r\n            if (toCol === fromCol - 2 &amp;&amp; board[fromRow - 1][fromCol - 1] !== 0 &amp;&amp; board[fromRow - 1][fromCol - 1].color != color) {\r\n                return true;\r\n            }\r\n\r\n            \/\/ right direction\r\n            if (toCol === fromCol + 2 &amp;&amp; board[fromRow - 1][fromCol + 1] !== 0 &amp;&amp; board[fromRow - 1][fromCol + 1].color != color) {\r\n                return true;\r\n            }\r\n        }\r\n    }\r\n\r\n    return false;\r\n}\r\n<\/pre>\n<p>The function restricts a piece movement only to diagonal jumping by 1 or 2 squares. In the case of a 2 squares jump there must be an enemy piece in the middle.<\/p>\n<p>Now here&#8217;s the <strong>pieceMoved<\/strong> function:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\r\nfunction pieceMoved(from, to, color) {\r\n    var fromRow = from[0];\r\n    var fromCol = from[1];\r\n    var toRow = to[0];\r\n    var toCol = to[1];\r\n\r\n    board[toRow][toCol] = board[fromRow][fromCol];\r\n\r\n    board[fromRow][fromCol] = 0;\r\n\r\n    \/\/ capture jumped piece\r\n    if (toRow === fromRow - 2) { \/\/ left direction\r\n        if (toCol === fromCol - 2) {\r\n            boardController.removePiece(fromRow - 1, fromCol - 1);\r\n            board[fromRow - 1][fromCol - 1] = 0;\r\n        } else {\r\n            boardController.removePiece(fromRow - 1, fromCol + 1);\r\n            board[fromRow - 1][fromCol + 1] = 0;\r\n        }\r\n    } else if (toRow === fromRow + 2) { \/\/ right direction\r\n        if (toCol === fromCol + 2) {\r\n            boardController.removePiece(fromRow + 1, fromCol + 1);\r\n            board[fromRow + 1][fromCol + 1] = 0;\r\n        } else {\r\n            boardController.removePiece(fromRow + 1, fromCol - 1);\r\n            board[fromRow + 1][fromCol - 1] = 0;\r\n        }\r\n    }\r\n\r\n    \/\/ change turn\r\n    if (color === CHECKERS.WHITE) {\r\n        colorTurn = CHECKERS.BLACK;\r\n    } else {\r\n        colorTurn = CHECKERS.WHITE;\r\n    }\r\n}\r\n\r\n<\/pre>\n<p>This function removes a captured (jumped) piece and toggles the turn to move. In order to remove a captured piece the <em>BoardController<\/em> needs one more method:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\r\nthis.addPiece = function (piece) {...};\r\n\r\nthis.removePiece = function (row, col) {\r\n    if (board[row][col]) {\r\n        scene.remove(board[row][col]);\r\n    }\r\n\r\n    board[row][col] = 0;\r\n};\r\n\r\n<\/pre>\n<p>That\u2019s all folks!\u00a0If you want to experiment more you could add some improvements to the game, like complete game logic or making the captured pieces fade out using <a title=\"Javascript Tweening Engine\" href=\"https:\/\/github.com\/sole\/tween.js\/\">tween.js<\/a>.<\/p>\n<p>You can download the sample project from <a href=\"https:\/\/www.osd.net\/blog\/wp-content\/uploads\/2013\/07\/webgl_3d_checkers_part3.zip\">here<\/a>.<\/p>\n<p>If you have questions, suggestions or improvements don\u2019t hesitate to add a comment and let me know about them.<\/p>\n<div id=\"share-and-beer-container\">\t<div id=\"buy_me_a_beer_div\" class=\"single beer\">\n\t \t\n\t\t<div class=\"buy-beer\" onclick=\"document.getElementById('buy_me_a_beer_form').submit();\">\n\t\t\t<form action=\"https:\/\/www.paypal.com\/cgi-bin\/webscr\" id=\"buy_me_a_beer_form\" method=\"post\">\n\t\t\t\t<input type=\"hidden\" name=\"cmd\" value=\"_xclick\" \/>\n\t\t\t\t<input type=\"hidden\" name=\"business\" value=\"info@directaccess.ro\" \/>  \n\t\t\t\t<input type=\"hidden\" name=\"item_name\" value=\"A Beer For 3D board game in a browser using WebGL and Three.js, part 3\" \/>  \n\t\t\t\t<input type=\"hidden\" name=\"item_number\" value=\"1\" \/>  \n\t\t\t\t<input type=\"hidden\" name=\"return\" value=\"https:\/\/www.osd.net\/blog\/web-development\/3d-board-game-in-a-browser-using-webgl-and-three-js-part-3\/\" \/>  \n\t\t\t\t<input type=\"hidden\" name=\"amount\" value=\"5\" \/>  \n\t\t\t\t<input type=\"hidden\" name=\"undefined_quantity\" value=\"1\" \/>  \n\t\t\t\t<input type=\"hidden\" name=\"currency_code\" value=\"USD\" \/>  \n\t\t\t<\/form>\n\t\t\t<p class=\"buy-beer-text\">If you liked this post <br \/> you can <strong>buy me a beer<\/strong><\/p>\n\t\t<\/div>\n\t<\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>Continuing from part 2 this final article will show you how to make the pieces move by drag and drop and adding some game logic. Note: Unless specified otherwise, the file you need to edit will be BoardController.js. Restrict camera rotation The first thing we need to do is to stop the camera from moving &hellip;<\/p>\n<div class=\"cta1\"><a href=\"https:\/\/www.osd.net\/blog\/web-development\/3d-board-game-in-a-browser-using-webgl-and-three-js-part-3\/\">Read more<\/a><\/div>\n<div class=\"like-excerpt\"><div\n        class=\"fb-like\"\n        data-href=\"https:\/\/www.osd.net\/blog\/web-development\/3d-board-game-in-a-browser-using-webgl-and-three-js-part-3\/\"\n        data-layout=\"button_count\"\n        data-action=\"like\"\n        data-show-faces=\"false\"\n        data-share=\"false\">\n        <\/div><\/div>","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[18,13],"tags":[122,121,120],"_links":{"self":[{"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/posts\/1100"}],"collection":[{"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/comments?post=1100"}],"version-history":[{"count":9,"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/posts\/1100\/revisions"}],"predecessor-version":[{"id":1120,"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/posts\/1100\/revisions\/1120"}],"wp:attachment":[{"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/media?parent=1100"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/categories?post=1100"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.osd.net\/blog\/wp-json\/wp\/v2\/tags?post=1100"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}