Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to translate Mouse X,Y Coordinates to move HTML element inside a container where container is rotated to some angle

I am developing a card game in HTML that shows real-time perspectives and card movements to all players.

All players are connected via socket.io.

BACKGROUND:

If there are 4 players, table is square and table HTML body is rotated based on player position on their screen.

The structure is like this:

<gameTable style=”position: relative;”> 

<card style=”position: absolute; top: 1%, left: 1%;”></card> 

</gameTable > 

Now in order to move cards, player picks a card with mouse and it

1: Saves that location

2: Based on movement, it converts movement of card (in px), into relatively equal percentage and then moves the card on table that much.

This is so that whether someone is using big screen or small screen, the card movement will be same for all people by using % as top, left coordinates.

-- SQUARE TABLE:

When player is in 0 degree table position, the mouse and card movements are directly linked.

Card top: --mouse y, card left: --mouse x

When player is in 180 degree table position, the mouse movements are reversed:

Card top: ++mouse y, card left: ++mouse x

For 90 degree:

Card top: ++mouse x, card left: --mouse y

Similar translation with small change of coordinates for 270 degree rotated table.

This works perfectly as with this translation, it perfectly translates mouse over all directions in code.

THE PROBLEM:

For a 6 player game, I want to use Hexagonal table. Now in this table, the players table rotate to:

0, 60, 120, 180, 240 and 300 degrees.

0 and 180 degree are fine but I am unable to figure out a proper formula to translate mouse coordinates to card coordinates based on rotation.

-- CURRENT SOLUTION (Not good)

Currently, I did some brute force programming and did it like this: (60 degree table)

If (mouse moving up / down) {

card top: --mouse y/1.7, card left: --mouse y

}

If (mouse moving left / right) {

card top: ++mouse x, card left: --mouse x/1.7

}

Similar approaches for other positions on hexa table with small changes.

This is brute force approach and it blocks me from transitioning from up/down movement to left/right state as I have to leave the mouse and then start again.

In short, it is not working and I would like to know if it is possible to have a formula that can correctly translate mouse coordinates of mouse to cards based on rotation.

Please view following images for visual representation of what I mean

Square Table

Hexa Table

like image 383
Hayder Ameen Avatar asked Apr 22 '21 00:04

Hayder Ameen


People also ask

Which function returns the coordinates of the box that the mouse coordinates are over?

Definition and Usage The clientX property returns the horizontal coordinate (according to the client area) of the mouse pointer when a mouse event was triggered.

Which method is used to get the Y coordinate of the point where user have clicked using mouse button?

Simple answer #1 use offsetX and offsetY offsetX; const y = e. offsetY; });

How do I find X and Y coordinates in canvas?

The x position of the canvas element, i.e. the left side of the rectangle can be found using the 'left' property. Similarly, the position of y-coordinate of the click is found by subtracting the event's y position with the bounding rectangle's y position.

How does the translate() CSS function work?

The translate () CSS function repositions an element in the horizontal and/or vertical directions. Its result is a <transform-function> data type. This transformation is characterized by a two-dimensional vector. Its coordinates define how much the element moves in each direction.

How do I move an element horizontally in HTML?

The first transition we'll add is translateX. This moves an element horizontally. When we click the button, a new class, translateX, will be appended to the cat image node. [00:28] Let's go ahead and add a translateX of a 100 pixels. When we click the button, we can see that the cat moves 100 pixels.

What is the ordinate of the translating vector?

The ordinate (vertical, y-coordinate) of the translating vector will be set to 0. For example, translate (2px) is equivalent to translate (2px, 0). A percentage value refers to the width of the reference box defined by the transform-box property.

How to move content to the left instead of the right?

You can always move content to the left instead of the right by using negative values: The above code will move the element with the .oranges class 150px to the left upon the user hovering their mouse over the element.


1 Answers

tl;dr See this JSFiddle

To move from four players to six players, you have to understand the math that underlies the four player case and then apply it to the six player case.

Four Player Case

You can think of each player's location as a given number of degrees (or radians) around a circle with an origin at the center of the table. In the four player case, the 1st player (index 0) will be at degrees 0, the 2nd player will be at 90 degrees, etc.

To determine how one player's movement affects the movement of a card on another person's screen that is not at the same angle, you need to break down the movement of the card in terms of its basis. In this case, the basis is composed of two orthogonal vectors that define the current player's movement space. The first vector is the vector generated using the angle. The second vector can be found by taking the current angle and adding 90 degrees (Pi/2 radians). For the four player case, the 1st player will have a primary basis vector of 0 degrees, which is (1,0). The 1st players secondary basis vector will be 0+90=90 degrees, which is (0,1).

To translate the mouse movement (delta screen x and y) into movement using an arbitrary basis, you take the dot product of delta screen x and y and the x and y of each vector in the basis. In the four player case, when the 1st player moves a card, the dot product of the primary and secondary basis result in the x delta and the y delta, respectively.

After you calculate this for the current player, you can to move the cards on other player's screens in terms of their basis. To do this, take the movement that has been converted into movement along the two bases and apply it to each other player's basis. In the four player case, the 2nd player's primary basis is 90 degrees = (1,0) and the secondary basis is 90+90 degrees = (-1,0). Thus to mirror the movement at the 2nd player's position, the original x movement is converted to y movement and the original y movement is converted to -x movement.

Six (or arbitrary number) of Players

To change this to a game with an arbitrary number of players and with a potential offset from 0 degrees for the 1st player, you need to follow the same math. First, calculate the bases for each player. Second, use dot products to calculate the delta of the card being movement in terms of its basis. Third, apply this movement to the bases of the other players.

The example below lets you change the number of players and the starting offset angle. The cards are divs and there is a canvas superimposed over the divs to show the primary axes (red) and the secondary axes (blue) for each basis.

<div>
<div class="table-div">
</div>
<div>
  This example shows how the current players card (blue) can be shown moving on other players' (red "ghost" cards).
  Below you
  can change the number of players and the angle offset of the starting player.
</div>
<div>
  <p>Players</p>
  <input type="text" onchange="playerChange()" id="playerText" value="6">
</div>
<div>
  <p>Angle (Degrees)</p>
  <input type="text" onchange="angleChange()" id="angleText" value="45">
</div>
<canvas id="canv"></canvas>
body {
  margin: 0;
}

canvas {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 9;
}

.table-div {
  background-color: lightgreen;
  width: 400px;
  height: 400px;
}

.card {
  position: absolute;
  width: 50px;
  height: 70px;
}

.current {
  background-color: blue;
}

.ghost {
  background-color: red;
}
//A simple class to hold an x,y pair
class Vector2 {
  constructor(x, y) { [this.x, this.y] = [x, y] }
}

//Set up our parameters
let playerCount = 6; //Can be changed by the user
let offsetAngle = 45; //Can be changed by the user
let width = 400;
let height = width;
let radius = width / 2;
let cardWidth = 50;
let cardHeight = 50 * 1.4;//~70
let cards;

//Track the mouse
let lastMouse = null;
let currentMouse = null;
let mouseDown = false;

//The angle in radians between each player on the table
let angleDelta;

//Grab the table div
let tableElement = document.querySelector(".table-div");
let canvas = document.querySelector("canvas")
let ctx = canvas.getContext("2d");
canvas.style.width = width + "px";
canvas.style.height = height + "px"
canvas.width = width;
canvas.height = height;

function update() {
  ctx.clearRect(0, 0, width, height);

  for (let i = 0; i < playerCount; i++) {
    //Draw the first axis
    ctx.strokeStyle = "red";
    ctx.beginPath();
    ctx.moveTo(radius, radius);
    ctx.lineTo(radius + Math.cos(angleDelta * i + offsetAngle) * radius, radius + Math.sin(angleDelta * i + offsetAngle) * radius)
    ctx.stroke();

    //Draw the second axis
    let secondAxisAngle = angleDelta * i + Math.PI / 2;
    let startX = radius + Math.cos(angleDelta * i + offsetAngle) * radius / 2;
    let startY = radius + Math.sin(angleDelta * i + offsetAngle) * radius / 2;
    let endX = Math.cos(secondAxisAngle + offsetAngle) * radius / 4;
    let endY = Math.sin(secondAxisAngle + offsetAngle) * radius / 4;

    ctx.strokeStyle = "green";
    ctx.beginPath();
    ctx.moveTo(startX, startY)
    ctx.lineTo(startX + endX, startY + endY)
    ctx.stroke();
  }
}

document.body.addEventListener("mousemove", e => {
  //Keep track of the last mouse position so we can calculate a delta
  lastMouse = currentMouse;
  currentMouse = new Vector2(e.clientX, e.clientY);
  if (lastMouse && mouseDown) {
    let mouseDelta = new Vector2(currentMouse.x - lastMouse.x, currentMouse.y - lastMouse.y);
    if (mouseDown) {
      //Determine the movement in the current player's basis
      let primaryAxisCurrent = new Vector2(Math.cos(angleDelta * 0 + offsetAngle), Math.sin(angleDelta * 0 + offsetAngle))
      let secondAxisCurrent = new Vector2(Math.cos(angleDelta * 0 + Math.PI / 2 + offsetAngle), Math.sin(angleDelta * 0 + Math.PI / 2 + offsetAngle))
      //Determine the movement in terms of the primary axes
      let primary = (primaryAxisCurrent.x * mouseDelta.x + primaryAxisCurrent.y * mouseDelta.y)// * mouseDelta.x;
      let second = (secondAxisCurrent.x * mouseDelta.x + secondAxisCurrent.y * mouseDelta.y)// * mouseDelta.y;

      //Update all the cards using the primary and secondary motion
      for (let i = 0; i < playerCount; i++) {
        //Get the axes for this card
        let primaryAxis = new Vector2(Math.cos(angleDelta * i + offsetAngle), Math.sin(angleDelta * i + offsetAngle))
        let secondAxis = new Vector2(Math.cos(angleDelta * i + Math.PI / 2 + offsetAngle), Math.sin(angleDelta * i + Math.PI / 2 + offsetAngle))
        //Translate the motion into this card's axes
        let primaryAxisMovement = new Vector2(primaryAxis.x * primary, primaryAxis.y * primary)
        let secondAxisMovement = new Vector2(secondAxis.x * second, secondAxis.y * second)

        //Get the current location (strip the 'px') and add the change in position.
        let div = cards[i];
        let location = new Vector2(parseFloat(div.style.left) + primaryAxisMovement.x + secondAxisMovement.x, parseFloat(div.style.top) + primaryAxisMovement.y + secondAxisMovement.y)
        //Reappend 'px'
        div.style.left = location.x + "px"
        div.style.top = location.y + "px"
      }
    }
  }
})

document.body.addEventListener("mousedown", () => mouseDown = true)
document.body.addEventListener("mouseup", () => mouseDown = false)

function initDivs() {
  //Create all the cards
  cards = [];
  tableElement.innerHTML = "";
  for (let i = 0; i < playerCount; i++) {
    let div = document.createElement("div");
    tableElement.appendChild(div);
    div.classList.add("card")
    if (i == 0) div.classList.add("current")
    else div.classList.add("ghost")

    let radians = angleDelta * i;
    div.style.left = (radius - cardWidth / 2 + Math.cos(radians + offsetAngle) * (radius - cardWidth / 2)) + "px";
    div.style.top = (radius - cardHeight / 2 + Math.sin(radians + offsetAngle) * (radius - cardHeight / 2)) + "px"
    cards.push(div);
  }
}
function playerChange() {
  playerCount = +document.querySelector("#playerText").value;
  angleDelta = Math.PI * 2 / playerCount;
  initDivs();
  angleChange();
}
function angleChange() {
  //Convert from degrees to radians
  offsetAngle = parseFloat(document.querySelector("#angleText").value) * Math.PI / 180;
  initDivs();
  update();
}

playerChange();
like image 110
bricksphd Avatar answered Oct 11 '22 19:10

bricksphd