- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
基本上,我有一个带有“子对象”的容器对象,这些对象相对于其父对象进行了修改,并且我想通过更改父对象的旋转值来旋转所有对象,同时保持各个子对象的方向稳定。 (如旋转整个对象)我觉得我没有很好地解释这一点,所以这里有两个例子。物理JS: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;
};
引用标记的兴趣点——这是我旋转 Canvas 的地方。如果对象有父对象,则按父对象的值加上对象的值进行旋转 - 否则,仅旋转对象的值。我尝试了一些不同的组合,例如...
• 父级 - 对象
• 对象 - 父级
...我查看了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 打算如何使用渲染。但我绝不会这样做。由于需要使用保存和恢复,它是帧速率的 killer ,这是因为 ctx.getTransform 支持非常有限(仅限 Chrome)。由于您无法获得必须在代码中镜像的变换,因此不必担心,因为如果您维护矩阵,则可以应用许多优化。您可以使用 setTransform 和一点数学来实时获得 1000 个 Sprite ,在 Canvas 四分之一或更糟糕的帧速率上这样做。
演示
使用安全递归运行示例。
以鼠标所在位置为中心绘制嵌套对象。
该演示只是从我拥有的其他一些代码中获取的递归渲染,并进行了剪切以适合该演示。它扩展了递归渲染以允许动画和渲染顺序。请注意,尺度不均匀,因此迭代越深入,就会出现一些偏差。
// 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);
关于javascript - 旋转一组对象,同时保持其方向不变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36708977/
我的一位教授给了我们一些考试练习题,其中一个问题类似于下面(伪代码): a.setColor(blue); b.setColor(red); a = b; b.setColor(purple); b
我似乎经常使用这个测试 if( object && object !== "null" && object !== "undefined" ){ doSomething(); } 在对象上,我
C# Object/object 是值类型还是引用类型? 我检查过它们可以保留引用,但是这个引用不能用于更改对象。 using System; class MyClass { public s
我在通过 AJAX 发送 json 时遇到问题。 var data = [{"name": "Will", "surname": "Smith", "age": "40"},{"name": "Wil
当我尝试访问我的 View 中的对象 {{result}} 时(我从 Express js 服务器发送该对象),它只显示 [object][object]有谁知道如何获取 JSON 格式的值吗? 这是
我有不同类型的数据(可能是字符串、整数......)。这是一个简单的例子: public static void main(String[] args) { before("one"); }
嗨,我是 json 和 javascript 的新手。 我在这个网站找到了使用json数据作为表格的方法。 我很好奇为什么当我尝试使用 json 数据作为表时,我得到 [Object,Object]
已关闭。此问题需要 debugging details 。目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and the
我听别人说 null == object 比 object == null check 例如: void m1(Object obj ) { if(null == obj) // Is thi
Match 对象 提供了对正则表达式匹配的只读属性的访问。 说明 Match 对象只能通过 RegExp 对象的 Execute 方法来创建,该方法实际上返回了 Match 对象的集合。所有的
Class 对象 使用 Class 语句创建的对象。提供了对类的各种事件的访问。 说明 不允许显式地将一个变量声明为 Class 类型。在 VBScript 的上下文中,“类对象”一词指的是用
Folder 对象 提供对文件夹所有属性的访问。 说明 以下代码举例说明如何获得 Folder 对象并查看它的属性: Function ShowDateCreated(f
File 对象 提供对文件的所有属性的访问。 说明 以下代码举例说明如何获得一个 File 对象并查看它的属性: Function ShowDateCreated(fil
Drive 对象 提供对磁盘驱动器或网络共享的属性的访问。 说明 以下代码举例说明如何使用 Drive 对象访问驱动器的属性: Function ShowFreeSpac
FileSystemObject 对象 提供对计算机文件系统的访问。 说明 以下代码举例说明如何使用 FileSystemObject 对象返回一个 TextStream 对象,此对象可以被读
我是 javascript OOP 的新手,我认为这是一个相对基本的问题,但我无法通过搜索网络找到任何帮助。我是否遗漏了什么,或者我只是以错误的方式解决了这个问题? 这是我的示例代码: functio
我可以很容易地创造出很多不同的对象。例如像这样: var myObject = { myFunction: function () { return ""; } };
function Person(fname, lname) { this.fname = fname, this.lname = lname, this.getName = function()
任何人都可以向我解释为什么下面的代码给出 (object, Object) 吗? (console.log(dope) 给出了它应该的内容,但在 JSON.stringify 和 JSON.parse
我正在尝试完成散点图 exercise来自免费代码营。然而,我现在只自己学习了 d3 几个小时,在遵循 lynda.com 的教程后,我一直在尝试确定如何在工具提示中显示特定数据。 This code
我是一名优秀的程序员,十分优秀!