旋转一组对象,同时保持其方向不变
Rotating a group of objects while keeping their orientation intact
基本上,我有一个容器对象,其中包含相对于其父对象修改的"子对象",我希望通过更改父对象的旋转值来旋转所有对象,同时保持单个子对象的方向稳定。(如,旋转整个对象)我觉得我没有很好地解释这一点,所以这里有两个例子。PhysicsJS:http://wellcaffeinated.net/PhysicsJS/(参见第一个例子,有0.7和球——注意当碰撞后旋转0或7时,物体的整体形状是如何保持的(http://phaser.io/examples/v2/groups/group-transform-rotate)与机器人。现在,我试着用自己的库复制前面提到的PhysicsJS示例,看看是否可以https://jsfiddle.net/khanfused/r4LgL5y9/(为简洁起见,已简化)
Art.prototype.modules.display.rectangle.prototype.draw = function() {
// Initialize variables.
var g = Art.prototype.modules.display.rectangle.core.graphics,
t = this;
// Execute the drawing commands.
g.save();
g.translate(t.parent.x ? t.parent.x + t.x : t.x, t.parent.y ? t.parent.y + t.y : t.y);
/* Point of interest. */
g.rotate(t.parent.rotation ? t.rotation : t.rotation);
g.scale(t.scale.x, t.scale.y);
g.globalAlpha = t.opacity === 'super' ? t.parent.opacity : t.opacity;
g.lineWidth = t.lineWidth === 'super' ? t.parent.lineWidth : t.lineWidth;
g.fillStyle = t.fill === 'super' ? t.parent.fill : t.fill;
g.strokeStyle = t.stroke === 'super' ? t.parent.stroke : t.stroke;
g.beginPath();
g.rect(t.width / -2, t.height / -2, t.width, t.height);
g.closePath();
if (t.fill) {
g.fill();
}
if (t.stroke) {
g.stroke();
}
g.restore();
return this;
};
参考标记的兴趣点——这是我旋转画布的地方。如果对象有一个父对象,它将按父对象的值加上对象的值进行旋转——否则,只旋转对象的值。我试过一些不同的组合,比如。。。
•父对象
•对象-父
我在PhysicsJS和Phaser的来源中寻找了一些正确的线索,但没有成功。
如何旋转组而不更改其布局?
若要变换一组对象,请使用要应用于组中所有成员的变换围绕该组,然后仅使用每个成员自己的变换来渲染每个成员。在每个成员通过其局部变换进行变换之前,您需要保存当前变换,以便将其用于下一个组成员。在渲染每个组成员结束时,您必须将转换恢复到其上方组的状态。
数据结构
group = {
origin : { x : 100, y : 100},
rotate : 2,
scale : { x : 1, y : 1},
render : function(){ // the function that draws to the canvas
ctx.strokeRect(-50,-50,100,100);
},
groups : [ // array of groups
{
origin : { x : 100, y : 100},
rotate : 2,
scale : { x : 1, y : 1},
render : function(){... }// draw something
groups : [] // could have more members
}], // the objects to be rendered
}
递归渲染
渲染嵌套转换最好通过递归来完成,其中renderGroup函数检查任何子组并调用自己来渲染该组。这使得用最少的代码生成复杂的嵌套对象变得非常容易。树是递归的一个简单例子,其中终止条件到达最后一个节点。但是,如果允许嵌套的组成员引用树中的其他成员,这很容易出错。这将导致Javascript阻塞页面并导致崩溃。
function renderGroup(group){
ctx.save();
// it is important that the order of transforms us correct
ctx.translate(group.origin.x, group.origin.y);
ctx.scale(group.scale.x, group.scale.y);
ctx.rotate(group.rotate);
// draw what is needed
if(group.render !== undefined){
group.render();
}
// now draw each member of this group.groups
for ( var i = 0 ; i < group.groups.length; i ++){
// WARNING this is recursive having any member of a group reference
// another member within the nested group object will result in an
// infinite recursion and computers just don't have the memory or
// speed to complete the impossible
renderGroup(group.groups[i]); // recursive call
};
// and finally restore the original transform
ctx.restore();
}
这就是嵌套转换的方式,以及W3C打算如何使用呈现。但我绝不会这样做。由于需要使用保存和恢复,它是帧速率的杀手,这是因为ctx.getTransform支持非常有限(仅Chrome)。由于您无法获得必须在代码中镜像的转换,因此没有必要,因为如果您维护矩阵,则可以应用许多优化。在那里,你可以使用setTransform和一点数学来实时获得1000个精灵,在画布四分之一或更糟的帧速率上这样做。
演示
使用安全递归运行示例。
以鼠标所在位置为中心绘制嵌套对象。
这个演示只是从我的其他代码中截取的递归渲染,并根据这个演示进行了裁剪。它扩展了递归渲染以允许动画和渲染顺序。请注意,尺度是不均匀的,因此迭代越深入,就会有一些偏斜。
// adapted from QuickRunJS environment.
//===========================================================================
// simple mouse
//===========================================================================
var mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, buttonRaw : 0,
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];}
e.preventDefault();
}
mouse.start = function(element, blockContextMenu){
if(mouse.element !== undefined){ mouse.removeMouse();}
mouse.element = element;
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
if(blockContextMenu === true){
element.addEventListener("contextmenu", preventDefault, false);
mouse.contextMenuBlocked = true;
}
}
mouse.remove = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(n => { mouse.element.removeEventListener(n, mouseMove); } );
if(mouse.contextMenuBlocked === true){ mouse.element.removeEventListener("contextmenu", preventDefault);}
mouse.contextMenuBlocked = undefined;
mouse.element = undefined;
}
}
return mouse;
})();
//===========================================================================
// fullscreen canvas
//===========================================================================
// delete needed for my QuickRunJS environment
function removeCanvas(){
if(canvas !== undefined){
document.body.removeChild(canvas);
}
canvas = undefined;
}
// create onscreen, background, and pixelate canvas
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){ createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.ctx = canvas.getContext("2d");
}
//===========================================================================
// general set up
//===========================================================================
var canvas,ctx;
canvas = undefined;
// create and size canvas
resizeCanvas();
// start mouse listening to canvas
mouse.start(canvas,true); // flag that context needs to be blocked
// listen to resize
window.addEventListener("resize",resizeCanvas);
var holdExit = 0; // To stop in QuickRunJS environment
var font = "18px arial";
//===========================================================================
// The following function are for creating render nodes.
//===========================================================================
// render functions
// adds a box render to a node;
function addBoxToNode(node,when,stroke,fill,lwidth,w,h){
function drawBox(){
ctx.strokeStyle = this.sStyle;
ctx.fillStyle = this.fStyle;
ctx.lineWidth = this.lWidth;
ctx.fillRect(-this.w/2,-this.h/2,this.w,this.h);
ctx.strokeRect(-this.w/2,-this.h/2,this.w,this.h);
}
var renderNode = {
render : drawBox,
sStyle : stroke,
fStyle : fill,
lWidth : lwidth,
w : w,
h : h,
}
node[when].push(renderNode);
return node;
}
// adds a text render to a node
function addTextToNode(node,when,text,x,y,fill){
function drawText(){
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = this.fStyle
ctx.fillText(this.text,this.x,this.y);
}
var renderNode = {
render : drawText,
text : text,
fStyle : fill,
x : x,
y : y,
}
node[when].push(renderNode); // binds to this node
return node;
}
// renders a node
function renderNode(renderList){
var i,len = renderList.length;
for(i = 0; i < len; i += 1){
renderList[i].render();
}
}
//---------------------------------------------------------------------------
// animation functions
// add a rotator to a node. Rotates the node
function addRotatorToNode(node,speed){
function rotator(){
this.transform.rot += this.rotSpeed;
}
node.animations.push(rotator.bind(node))
node.rotSpeed = speed;
}
// addd a wobbla to a nod. Wobbles the node
function addWobblaToNode(node,amount){
function wobbla(){
this.transform.sx = 1 - ((Math.cos(this.transform.rot) + 1) / 2) * this.scaleAmount ;
this.transform.sy = 1 - ((Math.sin(this.transform.rot) + 1) / 2) * this.scaleAmount ;
}
node.animations.push(wobbla.bind(node))
node.scaleAmount = amount;
}
// add a groover to a node. Move that funcky thang.
function addGrooverToNode(node,amount){
function wobbla(){
this.transform.x += Math.cos(this.transform.rot) * this.translateDist ;
this.transform.y += Math.sin(this.transform.rot*3) * this.translateDist ;
}
node.animations.push(wobbla.bind(node))
node.translateDist = amount;
}
// function to animate and set a transform
function setTransform(){
var i, len = this.animations.length;
for(i = 0; i < len; i ++){ // do any animtions that are on this node
this.animations[i]();
}
// set the transfomr
ctx.scale(this.transform.sx, this.transform.sy);
ctx.translate(this.transform.x, this.transform.y);
ctx.rotate(this.transform.rot);
}
//---------------------------------------------------------------------------
// node creation
// creats a node and returns it
function createNode(){
return {
transform : undefined,
setTransform : setTransform, // function to apply the current transform
animations : [], // animation functions
render : renderNode, // render main function
preRenders : [], // render to be done befor child nodes are rendered
postRenders : [], // render to be done after child nodes are rendered
nodes : [],
itterationCounter : 0, // important counts iteration depth
};
}
function addNodeToNode(node,child){
node.nodes.push(child);
}
// adds a transform to a node and returns the transform
function createNodeTransform(node,x,y,sx,sy,rot){
return node.transform = {
x : x, // translate
y : y,
sx : sx, //scale
sy : sy,
rot : rot, //rotate
};
}
// only one top node
var nodeTree = createNode(); // no details as yet
// add a transform to the top node and keep a ref for moving
var topTransform = createNodeTransform(nodeTree,0,0,1,1,0);
// top node has no render
var boxNode = createNode();
createNodeTransform(boxNode,0,0,0.9,0.9,0.1)
addRotatorToNode(boxNode,-0.02)
addWobblaToNode(boxNode,0.2)
addBoxToNode(boxNode,"preRenders","Blue","rgba(0,255,0,0.2)",3,100,100)
addTextToNode(boxNode,"postRenders","FIRST",0,0,"red")
addTextToNode(boxNode,"postRenders","text on top",0,20,"red")
addNodeToNode(nodeTree,boxNode)
function Addnode(node,x,y,scale,rot,text,anRot,anSc,anTr){
var boxNode1 = createNode();
createNodeTransform(boxNode1,x,y,scale,scale,rot)
addRotatorToNode(boxNode1,anRot)
addWobblaToNode(boxNode1,anSc)
addGrooverToNode(boxNode1,anTr)
addBoxToNode(boxNode1,"preRenders","black","rgba(0,255,255,0.2)",3,100,100)
addTextToNode(boxNode1,"postRenders",text,0,0,"black")
addNodeToNode(node,boxNode1)
// add boxes to coners
var boxNode2 = createNode();
createNodeTransform(boxNode2,50,-50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
var boxNode2 = createNode();
createNodeTransform(boxNode2,-50,-50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
var boxNode2 = createNode();
createNodeTransform(boxNode2,-50,50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
var boxNode2 = createNode();
createNodeTransform(boxNode2,50,50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
}
Addnode(boxNode,50,50,0.9,2,"bot right",-0.01,0.1,0);
Addnode(boxNode,50,-50,0.9,2,"top right",-0.02,0.2,0);
Addnode(boxNode,-50,-50,0.9,2,"top left",0.01,0.1,0);
Addnode(boxNode,-50,50,0.9,2,"bot left",-0.02,0.2,0);
//===========================================================================
// RECURSIVE NODE RENDER
//===========================================================================
// safety var MUST HAVE for those not used to recursion
var recursionCount = 0; // number of nodes
const MAX_RECUSION = 30; // max number of nodes to itterate
// safe recursive as global recursion count will limit nodes reandered
function renderNodeTree(node){
var i,len;
// safty net
if((recursionCount ++) > MAX_RECUSION){
return;
}
ctx.save(); // save context state
node.setTransform(); // animate and set transform
// do pre render
node.render(node.preRenders);
// render each child node
len = node.nodes.length;
for(i = 0; i < len; i += 1){
renderNodeTree(node.nodes[i]);
}
// do post renders
node.render(node.postRenders);
ctx.restore(); // restore context state
}
//===========================================================================
// RECURSIVE NODE RENDER
//===========================================================================
ctx.font = font;
function update(time){
ctx.setTransform(1,0,0,1,0,0); // reset top transform
ctx.clearRect(0,0,canvas.width,canvas.height);
// set the top transform to the mouse position
topTransform.x = mouse.x;
topTransform.y = mouse.y;
recursionCount = 0;
renderNodeTree(nodeTree);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
- 引用对象中的通用值
- jQuery匹配JSON对象的部分文本
- 节点导出返回一个空对象
- 如何在Javascript中将JSon对象转换为数组
- 我可以在json对象中添加一个函数吗
- 使用JS将数组转换为json对象
- ng映射方向备选方案
- 全局变量和全局对象的属性之间有什么区别吗
- 比较从函数和生成的日期对象
- Javascript,访问一个主要对象模块模式中的每个对象
- 无法读取数据“;对象名称“;未定义的角度方向
- 旋转一组对象,同时保持其方向不变
- 三.js获取具有给定世界方向坐标的场景矢量的对象相对局部方向
- 根据键盘 threejs 更改对象旋转动画方向
- 如何使用方向路由对象获取数据
- 如何测试方向的相等性服务请求对象
- 如何在Three.js中沿摄影机方向将对象直接从摄影机移开
- HTML画布以恒定的速度在一个方向上移动一个对象
- 如何使用document.onkeydown用方向键移动对象
- fabric.js确定对象的方向:移动