简体   繁体   English

旋转一组对象,同时保持其方向不变

[英]Rotating a group of objects while keeping their orientation intact

Basically I have a container object with 'children' that are modified relative to their parent, and I want to rotate all of the objects by changing the parent's rotation value, while keeping the orientation of the individual children stable. 基本上,我有一个带有“子项”的容器对象,相对于其父项进行了修改,我想通过更改父项的旋转值来旋转所有对象,同时保持各个子项的方向稳定。 (as in, rotate the whole object) I feel like I'm not explaining this very well, so here are two examples. (例如,旋转整个对象)我觉得我对此解释得不太好,所以这里有两个例子。 PhysicsJS: http://wellcaffeinated.net/PhysicsJS/ (see the first example, with the 0.7 and the balls -- notice how when the zero or seven are rotated after a collision the overall shape of the object is maintained. Same goes for this example in PhaserJS ( http://phaser.io/examples/v2/groups/group-transform-rotate ) with the robot. Now, just to see if I could, I tried to duplicate the aforementioned PhysicsJS example with my own library -- https://jsfiddle.net/khanfused/r4LgL5y9/ (simplified for brevity) PhysicsJS: http ://wellcaffeinated.net/PhysicsJS/(请参见第一个示例,带有0.7和球-注意碰撞后零或七旋转时如何保持对象的整体形状。这个在机器人的PhaserJS( 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;

};

Refer to the labeled point of interest -- that's where I rotate the canvas. 请参考标记的兴趣点-那是我旋转画布的地方。 If the object has a parent, it's rotated by the parent's value plus the object's value -- otherwise, just the object's value. 如果对象具有父对象,则将其旋转父对象的值加上对象的值-否则,仅对象的值。 I've tried some different combinations, like... 我尝试了一些不同的组合,例如...

• parent - object •父对象
• object - parent •对象-父对象

...and I looked through PhysicsJS and Phaser's sources for some kind of clue in the right direction, to no avail. ...然后我查看了PhysicsJS和Phaser的资源,找到了正确方向的某种线索,但无济于事。

How do I rotate a group but not change its layout? 如何旋转组但不更改其布局?

Nested Transform 嵌套变换

To transform a group of objects surround the group with the transform you wish to apply to all the members of the group and then just render each member with its own transform. 要对一组对象进行变换,请将该变换应用于该组,您希望将其应用于该组的所有成员,然后仅使用其自己的变换渲染每个成员。 Before each member is transformed by its local transform you need to save the current transform so it can be used for the next group member. 在通过本地变换对每个成员进行变换之前,您需要保存当前变换,以便可以将其用于下一个组成员。 At the end of rendering each group member you must restore the transform back to the state for the group above it. 在渲染每个组成员的最后,您必须将变换还原回其上方的组的状态。

The data structure 数据结构

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
}

Recursive rendering 递归渲染

Rendering nested transformations is best done via recursion where the renderGroup function checks for any sub groups and calls itself to render that group. 渲染嵌套的转换最好通过递归完成,在该递归中,renderGroup函数检查任何子组并调用自身以呈现该组。 This makes it very easy to have complex nested objects with the minimum of code. 这使得使用最少的代码来拥有复杂的嵌套对象变得非常容易。 A tree is a simple example of recursion where the terminating condition is reaching the last node. 树是递归的简单示例,其中终止条件到达最后一个节点。 But this can easily go wrong if you allow nested group members to reference other members within the tree. 但是,如果允许嵌套的组成员引用树中的其他成员,这很容易出错。 This will result in Javascript blocking the page and a crash. 这将导致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();
}

That is how to nest transforms and how the W3C has intended for the render to be used. 这就是嵌套转换的方式,以及W3C如何使用渲染的方式。 But I would never do it this way. 但是我永远不会这样。 It is a killer of frame rate due to the need to use save and restore, this is because ctx.getTransform support is very limited (only Chrome). 由于需要使用保存和还原,因此它是帧速率的杀手,,这是因为ctx.getTransform支持非常有限(仅Chrome)。 As you can not get the transform you must mirror is in code, needless as there are many optimisations that can be applied if you are maintaining the matrix. 由于无法获取转换,因此必须在代码中进行镜像,这是不必要的,因为如果要维护矩阵,可以应用许多优化。 Where you may get 1000 sprites in realtime using setTransform and a little math, doing it this way on canvas quarters or worse the frame rate. 在这里,您可以使用setTransform和一些数学运算来实时获取1000个Sprite,在画布上以这种方式执行此操作,否则会降低帧速率。

Demo 演示版

Running example with safe recursion. 具有安全递归的运行示例。

Draws nested objects centered on where the mouse is. 以鼠标所在的位置为中心绘制嵌套对象。

The demo is simply a recursive render taken from some other code I have and cut to suit this demo. 该演示只是从我拥有的其他一些代码中提取的递归渲染,并进行了裁剪以适合该演示。 It extends the recursive render to allow for animation and render order. 它扩展了递归渲染以允许动画和渲染顺序。 Note that the scales are non uniform thus there will be some skewing the deeper the iterations go. 请注意,比例尺是不均匀的,因此迭代越深入,就会有些偏差。

 // 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; mx = e.offsetX; my = e.offsetY; if (mx === undefined) { mx = e.clientX; my = 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); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将新值推送到对象数组,同时保持旧值不变 React Js Next Js - Pushing New value to Array of Objects while keeping old one intact React Js Next Js Rails形式:重新加载数据,同时保持修改后的数据不变 - Rails Form: Reload data while keeping modified data intact jQuery-用替换html按钮 <li> 同时保持价值不变 - JQuery - Replace html button with <li> while keeping values intact 在Javascript中替换字符串同时保持JSON文件不变 - Replacing a string in Javascript while keeping JSON file intact 在跟踪锚点的同时旋转符号 - Rotating symbols while keeping track of the anchor CSS3 &amp; HTML5 - 旋转对象组 - CSS3 & HTML5 - Rotating group of objects 旋转设备Google Chrome时显示方向错误 - Orientation is shown wrong while rotating device Google chrome 如何使用javascript修改字符串中每个单词的第一个字母,同时保持内部HTML完好无损? - How to modify the first letter of each word in a string using javascript while keeping inner HTML intact? 在Angular JS模板中,在父元素之间交替,同时保持子元素完整 - In Angular JS Templates, alternate between parent elements while keeping children intact 如何在 Puppeteer 生成的 pdf 页面的文本中添加边距,同时保持背景颜色不变 - How to add margins to text of pdf pages generated by Puppeteer while keeping the background color intact
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM