我有一个Html5画布,我正在绘制正方形。 画布本身大致与窗口大小相同。 当我在方块上检测到点击时,我想翻译画布,使广场大致位于窗口的中心。任何见解,提示或直截了当的回复都是受欢迎的。
这是我到目前为止所尝试的内容:
如果一个正方形位于点(1000,1000),我只需翻译画布(-1000,-1000)。我知道我需要添加一个偏移量,以便它在窗口中居中。但是,画布总是从可见窗口结束(在某处的左上角太远)。
更复杂的情况:
最终,我希望能够以转换(旋转和倾斜)的画布上的单击对象为中心。我正在寻找一种效果非常好的等长效果。 我想知道这种转变是否会影响居中的逻辑/数学?
答案 0 :(得分:0)
使用非标准轴(或投影)(如isometrix)时,最好使用变换矩阵。它将涵盖具有相同简单功能的每个可能的2D投影。
iso世界的坐标称为世界坐标。所有对象都存储为世界坐标。渲染它们时,使用变换矩阵将这些坐标投影到屏幕坐标。
矩阵表示世界屏幕坐标的方向和大小 x和y轴以及世界原点的屏幕位置(0,0)
对于
的iso所以矩阵作为数组
const isoMat = [1,0.5,-1,0.5,0,0]; // ISO (pixel art) dimorphic projection
前两个是x轴,接下来两个是y轴,后两个值是原点的屏幕坐标。
将矩阵应用于点,这会将点从一个坐标系转换为另一个坐标系。您也可以通过逆变换转换回来。
您需要将世界坐标转换为屏幕坐标。
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2] + isoMat[4];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3] + isoMat[5];
}
在演示中,我忽略了原点,因为我始终将它设置在画布的中心。因此从该函数中删除原点
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];
}
您还需要将屏幕坐标转换为世界。为此,您需要使用逆变换。它有点像乘法的倒数a * 2 = b
是b / 2 = a
有一种计算逆矩阵的标准方法如下
const invMatrix = []; // inverse matrix
// I call the next line cross, most call it the determinant which I
// think is stupid as it is effectively a cross product and is used
// like you would use a cross product. Anyways I digress
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] = isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] = isoMat[0] / cross;
然后我们有一个从屏幕x,y转换到世界位置的函数
function screenToWorld(pos,retPos){
const x = pos.x - isoMat[4];
const y = pos.y - isoMat[5];
retPos.x = x * invMatrix[0] + y * invMatrix[2];
retPos.y = x * invMatrix[1] + y * invMatrix[3];
}
因此,您将鼠标坐标作为屏幕像素,使用上述功能转换为世界坐标。然后你可以使用世界坐标找到你想要的对象。
要将世界对象移动到屏幕中心,您可以将其坐标转换为屏幕坐标,在屏幕上添加位置(画布中心)并将变换矩阵原点设置为该位置。
该演示在世界坐标中创建一组框。它通过isoMat
ctx.setTransform(
(等轴测投影)
每一帧我将鼠标屏幕转换为世界坐标,然后用它来检查鼠标在哪个框上。
如果鼠标按钮关闭,我将该框从世界坐标转换为屏幕并添加屏幕中心。为了平滑步骤,新的屏幕中心被追逐(平滑)..
那么你应该能够在代码中解决它,在评论中提出任何问题。
const ctx = canvas.getContext("2d");
const moveSpeed = 0.4;
const boxMin = 20;
const boxMax = 50;
const boxCount = 100;
const boxArea = 2000;
// some canvas vals
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
const U = undefined;
// Helper function
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
const setOf = (count, cb) => {var a = [],i = 0; while (i < count) { a.push(cb(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// mouse function and object
const mouse = {x : 0, y : 0, button : false, world : {x : 0, y : 0}}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// boxes in world coordinates.
const boxes = [];
function draw(){
if(this.dead){
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.fillRect(this.x,this.y,this.w,this.h);
}
ctx.strokeStyle = this.col;
ctx.globalAlpha = 1;
ctx.strokeRect(this.x,this.y,this.w,this.h);
// the rest is just overkill
if(this.col === "red"){
this.mr = 10;
}else{
this.mr = 1;
}
this.mc += (this.mr-this.m) * 0.45;
this.mc *= 0.05;
this.m += this.mc;
for(var i = 0; i < this.m; i ++){
const m = this.m * (i + 1);
ctx.globalAlpha = 1-(m / 100);
ctx.strokeRect(this.x-m,this.y-m,this.w,this.h);
}
}
// make random boxes.
function createBoxes(){
boxes.length = 0;
boxes.push(...setOf(boxCount,()=>{
return {
x : randI(cw- boxArea/ 2, cw + boxArea/2),
y : randI(ch- boxArea/ 2, ch + boxArea/2),
w : randI(boxMin,boxMax),
h : randI(boxMin,boxMax),
m : 5,
mc : 0,
mr : 5,
col : "black",
dead : false,
draw : draw,
isOver : isOver,
}
}));
}
// use mouse world coordinates to find box under mouse
function isOver(x,y){
return x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h;
}
var overBox;
function findBox(x,y){
if(overBox){
overBox.col = "black";
}
overBox = undefined;
eachOf(boxes,box=>{
if(box.isOver(x,y)){
overBox = box;
box.col = "red";
return true;
}
})
}
function drawBoxes(){
boxes.forEach(box=>box.draw());
}
// next 3 values control the movement of the origin
// rather than move instantly the currentPos chases the new pos.
const currentPos = {x :0, y : 0};
const newPos = {x :0, y : 0};
const chasePos = {x :0, y : 0};
// this function does the chasing
function updatePos(){
chasePos.x += (newPos.x - currentPos.x) * moveSpeed;
chasePos.y += (newPos.y - currentPos.y) * moveSpeed;
chasePos.x *= moveSpeed;
chasePos.y *= moveSpeed;
currentPos.x += chasePos.x;
currentPos.y += chasePos.y;
}
// ISO matrix and inverse matrix plus 2world and 2 screen
const isoMat = [1,0.5,-1,0.5,0,0];
const invMatrix = [];
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] = isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] = isoMat[0] / cross;
function screenToWorld(pos,retPos){
const x = pos.x - isoMat[4];
const y = pos.y - isoMat[5];
retPos.x = x * invMatrix[0] + y * invMatrix[2];
retPos.y = x * invMatrix[1] + y * invMatrix[3];
}
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];// + isoMat[4];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];// + isoMat[5];
}
// main update function
function update(timer){
// standard frame setup
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
createBoxes();
}else{
ctx.clearRect(0,0,w,h);
}
ctx.fillStyle = "black";
ctx.font = "28px arial";
ctx.textAlign = "center";
ctx.fillText("Click on a box to center it.",cw,28);
// update position
updatePos();
isoMat[4] = currentPos.x;
isoMat[5] = currentPos.y;
// set the screen transform to the iso matrix
// all drawing can now be done in world coordinates.
ctx.setTransform(isoMat[0], isoMat[1], isoMat[2], isoMat[3], isoMat[4], isoMat[5]);
// convert the mouse to world coordinates
screenToWorld(mouse,mouse.world);
// find box under mouse
findBox(mouse.world.x, mouse.world.y);
// if mouse down and over a box
if(mouse.button && overBox){
mouse.button = false;
overBox.dead = true; // make it gray
// get the screen coordinates of the box
worldToScreen({
x:-(overBox.x + overBox.w/2),
y:-(overBox.y + overBox.h/2),
},newPos
);
// move it to the screen center
newPos.x += cw;
newPos.y += ch;
}
// forget what the following function does, think it does something like draw boxes, but I am guessing.. :P
drawBoxes();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>