I'm play around with the CAKeyframeAnimation in order to better understanding how this type of animation work.
I want to move my SCNnode in a square shape and at every corner rotate his eulerangle Y of 90 degrees to make it follow the orientation of the track
here my code
func animatePlaneKey(nodeToAnimate: SCNNode){
// move forward
let pos = nodeToAnimate.position
let animation = CAKeyframeAnimation(keyPath: "position")
let pos1 = SCNVector3(pos.x, pos.y, pos.z)
let pos2 = SCNVector3(pos.x + 1 , pos.y, pos.z)
let pos3 = SCNVector3(pos.x + 1 , pos.y, pos.z + 1) // 1
let pos4 = SCNVector3(pos.x - 1 , pos.y, pos.z + 1)
let pos5 = SCNVector3(pos.x, pos.y, pos.z)
let easeIn = CAMediaTimingFunction(controlPoints: 0.35, 0.0, 1.0, 1.0)
animation.values = [pos1,pos2, pos3,pos4,pos5]
animation.keyTimes = [0,0.25,0.5,0.75,1]
animation.timingFunctions = [easeIn]
animation.calculationMode = .cubic
animation.duration = 12
animation.repeatCount = 1
animation.isAdditive = false
animation.autoreverses = false
// Heading 1st
let firstTurnAnim = CAKeyframeAnimation(keyPath: "eulerAngles.y")
let heading = nodeToAnimate.eulerAngles.y
let rot0heading = heading
let rot2heading = heading - Float(deg2rad(90))
firstTurnAnim.values = [rot0heading,rot2heading]
firstTurnAnim.keyTimes = [0.2,0.3]
firstTurnAnim.duration = 3
firstTurnAnim.repeatCount = 1
firstTurnAnim.isAdditive = true
firstTurnAnim.autoreverses = false
// // Heading 2st
let secondTurnAnim = CAKeyframeAnimation(keyPath: "eulerAngles.y")
let heading2 = nodeToAnimate.eulerAngles.y
let rot1head0 = heading2
let rot1head1 = heading2 - Float(deg2rad(180))
secondTurnAnim.values = [rot1head0,rot1head1]
secondTurnAnim.keyTimes = [0.45,0.55]
secondTurnAnim.duration = 6
secondTurnAnim.repeatCount = 1
secondTurnAnim.isAdditive = true
secondTurnAnim.autoreverses = false
nodeToAnimate.addAnimation(animation, forKey: "movement")
nodeToAnimate.addAnimation(firstTurnAnim, forKey: "turn")
nodeToAnimate.addAnimation(secondTurnAnim, forKey: "turn2")
}
I'm struggling to combine the animation of the y axis at the correct time.
when I add the "turn2" the animation start to mess up everything's, the node appear already rotate in the wrong direction.
For my understanding turn2 animation should start at keyframe 0.45 and finish at 0.55, why it start immediately ?
any idea what should be the correct way to combine this animation?
Related
I used tmap to create the plot attached. However, I would like to add a scale bar to the inset map, but I haven't been able to figured out how to do that. Can someone please help me?
Here are the codes that I used to create the attached map:
main_map <- tmap::tm_shape(main_map_df) +
tmap::tm_polygons(
col = "var.q5",
palette = c("#CCCCCC", "#999999", "#666666", "#333333", "#000000"),
#alpha = 0.7,
lwd = 0.5,
title = "") +
tmap::tm_layout(
frame = FALSE,
legend.outside = TRUE,
legend.hist.width = 5,
legend.text.size = 0.5,
fontfamily = "Verdana") +
tmap::tm_scale_bar(
position = c("LEFT", "BOTTOM"),
breaks = c(0, 10, 20),
text.size = 0.5
) +
tmap::tm_compass(position = c("LEFT", "TOP"))
inset_map <- tmap::tm_shape(inset_map_df) +
tmap::tm_polygons() +
tmap::tm_shape(main_map_df) +
tm_fill("grey50") +
tmap::tm_scale_bar(
position = c("LEFT", "BOTTOM"),
breaks = c(0, 10, 20),
text.size = 0.5
)
# Combine crude rate map (inset + main) =====
tiff(
"main_map_w_iset.tiff",
height = 1200,
width = 1100,
compression = "lzw",
res = 300
)
main_map
print(
inset_map,
vp = viewport(
x = 0.7,
y = 0.18,
width = 0.3,
height = 0.3,
clip = "off")
)
dev.off()
Thank you!
Here's a simple example using the World data set:
library(tidyverse)
library(tmap)
library(grid)
data("World")
# main map
tm_main <- World %>%
filter(name == "Australia") %>%
tm_shape() +
tm_polygons(col = "red",
alpha = .5) +
tm_scale_bar()
# inset map
tm_inset <- tm_shape(World) +
tm_polygons(col = "gray",
alpha = .5) +
tm_scale_bar()
vp <- viewport(x = .615, y = .5, width = .6, height = .6, just = c("right", "top"))
# final map
tmap_save(tm_main, filename = "test_inset.png", insets_tm = tm_inset, insets_vp = vp,
height = 200, width = 200, units = "mm")
Hi have a component that moves an object from its position into scene to a position in front of the camera.
This works fine if the camera has not been rotated, but I can't get it to account for the camera rotation when it has been rotated.
//Copy the initial datas
this._initialPosition = this._threeElement.position.clone()
this._initialQuaternion = this._threeElement.quaternion.clone()
//Convert fov + reduce it
let fovInRad = AFRAME.THREE.Math.degToRad(this._cameraEntity.fov)/2
let ratio=window.innerWidth/window.innerHeight //Assuming the FOV is vertical
let pLocal,cPos
let sizeY = this._size.y
let sizeX = this._size.x
let sizeZ = this._size.z
sizeX*=this._threeElement.scale.x
sizeY*=this._threeElement.scale.y
sizeZ*=this._threeElement.scale.x
const uSpace= 0.8
sizeY/=2;sizeX/=2
sizeY*=uSpace;sizeX*=uSpace
let tanFov = Math.tan(fovInRad)
let distY = sizeY/tanFov
let distX = ((sizeX/(ratio*tanFov)) < distY) ? distY : sizeX/(ratio*tanFov)
pLocal = new AFRAME.THREE.Vector3(0, 0, -(distX + sizeZ))
cPos = this._cameraEntity.position.clone()
cPos.y += 1.6
this._targetPosition = pLocal.applyMatrix4(this._cameraEntity.matrixWorld.clone())
this._threeElement.parent.worldToLocal(this._targetPosition)
let targetLook = cPos.applyMatrix4(this._cameraEntity.matrixWorld.clone())
this._threeElement.parent.worldToLocal(targetLook)
this._threeElement.position.copy(this._targetPosition)
this._threeElement.lookAt(targetLook)
this._targetQuaternion = this._threeElement.quaternion.clone()
The issue seems to be when I apply the matrix of the camera.
Any idea how to find this target position relative to where the camera is facing
New three.vector3(0,0,-camera.near).applyQuaternion(camera.quaternion).add(camera.position)
I'm testing PIXIjs for a simple 2D graphics, basically I'm sliding tiles with some background color and borders animation, plus I'm masking some parts of the layout.
While it works great in desktops it's really slower than the same slide+animations made with pure css in mobile devices (where by the way I'm using crosswalk+cordova so the browser is always the same)
For moving tiles and animating color I'm calling requestAnimationFrame for each tile and I've disabled PIXI's ticker:
ticker.autoStart = false;
ticker.stop();
This slowness could be due to a weaker GPU on mobiles? or is just about the way I use PIXI?
I'm not showing the full code because is quite long ~ 800 lines.
The following is the routine I use for each tile once a slide is captured:
const animateTileBorderAndText = (tileObj, steps, _color, radius, textSize, strokeThickness, _config) => {
let pixiTile = tileObj.tile;
let s = 0;
let graphicsData = pixiTile.graphicsData[0];
let shape = graphicsData.shape;
let textStyle = pixiTile.children[0].style;
let textInc = (textSize - textStyle.fontSize) / steps;
let strokeInc = (strokeThickness - textStyle.strokeThickness) / steps;
let prevColor = graphicsData.fillColor;
let color = _color !== null ? _color : prevColor;
let alpha = pixiTile.alpha;
let h = shape.height;
let w = shape.width;
let rad = shape.radius;
let radiusInc = (radius - rad) / steps;
let r = (prevColor & 0xFF0000) >> 16;
let g = (prevColor & 0x00FF00) >> 8;
let b = prevColor & 0x0000FF;
let rc = (color & 0xFF0000) >> 16;
let rg = (color & 0x00FF00) >> 8;
let rb = color & 0x0000FF;
let redStep = (rc - r) / steps;
let greenStep = (rg - g) / steps;
let blueStep = (rb - b) / steps;
let paintColor = prevColor;
let goPaint = color !== prevColor;
let animate = (t) => {
if (s === steps) {
textStyle.fontSize = textSize;
textStyle.strokeThickness = strokeThickness;
//pixiTile.tint = color;
if (!_config.SEMAPHORES.slide) {
_config.SEMAPHORES.slide = true;
PUBSUB.publish(_config.SLIDE_CODE, _config.torusModel.getData());
}
return true;
}
if (goPaint) {
r += redStep;
g += greenStep;
b += blueStep;
paintColor = (r << 16) + (g << 8) + b;
}
textStyle.fontSize += textInc;
textStyle.strokeThickness += strokeInc;
pixiTile.clear()
pixiTile.beginFill(paintColor, alpha)
pixiTile.drawRoundedRect(0, 0, h, w, rad + radiusInc * (s + 1))
pixiTile.endFill();
s++;
return requestAnimationFrame(animate);
};
return animate();
};
the above function is called after the following one, which is called for each tile to make it slide.
const slideSingleTile = (tileObj, delta, axe, conf, SEM, tilesMap) => {
let tile = tileObj.tile;
let steps = conf.animationSteps;
SEM.slide = false;
let s = 0;
let stepDelta = delta / steps;
let endPos = tile[axe] + delta;
let slide = (time) => {
if (s === steps) {
tile[axe] = endPos;
tileObj.resetPosition();
tilesMap[tileObj.row][tileObj.col] = tileObj;
return tileObj.onSlideEnd(axe == 'x' ? 0 : 2);
}
tile[axe] += stepDelta;
s++;
return requestAnimationFrame(slide);
};
return slide();
};
For each finger gesture a single column/row (of NxM matrix of tiles) is slided and animated using the above two functions.
It's the first time I use canvas.
I red that canvas is way faster then DOM animations and I red very good review of PIXIjs, so I believe I'm doing something wrong.
Can someone help?
In the end I'm a complete donk...
The issue is not with pixijs.
Basically I was forcing 60fps! The number of steps to complete the animation is set to 12 that implies 200ms animation at 60FPS (using requestAnimationFrame) but in low end devices its going to be obviously slower.
Css animation works with timing as parameter so it auto adapt FPS to devices hardware.
To solve the issue I'm adapting the number of steps during animations, basically if animations takes longer than 200ms I just reduce number of steps proportionally.
I hope this could be of help for each web developer used to css animation who have just started developing canvas.
Here is problem in which I add zoombie sprite to the scene every one second. When I add another sub animated zoombie to the zoombie node, sometimes it loads animated texture, and other times appear red large X.
func addMonster() {
let zoombieSprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(40, 60))
// Determine where to spawn the monster along the Y axis
let actualY = randRange(lower: zoombieSprite.size.height, upper: size.height - zoombieSprite.size.height)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
zoombieSprite.position = CGPoint(x: size.width + zoombieSprite.size.width/2, y: actualY)
zoombieSprite.physicsBody = SKPhysicsBody(rectangleOfSize: zoombieSprite.size) // 1
zoombieSprite.physicsBody?.dynamic = true // 2
zoombieSprite.physicsBody?.categoryBitMask = PhysicsCategory.Monster // 3
zoombieSprite.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile // 4
zoombieSprite.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
addChild(zoombieSprite)
//zoombieSprite.addChild(createAnimatedZoombie())
let zoombieAnimation = SKAction.runBlock({
zoombieSprite.addChild(self.createAnimatedZoombie())
})
// Determine speed of the monster
let actualDuration = randRange(lower: 6.0, upper: 10.0)
//print("actualDuration = \(actualDuration)")
let actionMove = SKAction.moveTo(CGPoint(x: -zoombieSprite.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
// Create the actions
let actionMoveDone = SKAction.removeFromParent()
zoombieSprite.runAction(SKAction.sequence([zoombieAnimation ,actionMove,actionMoveDone]))
}
//MARK: - ANIMATE FRAME AND MOVE ZOOMBIE
func createAnimatedZoombie () -> SKSpriteNode {
let animatedZoobieNode = SKSpriteNode(texture: spriteArray[0])
let animationFrameAction = SKAction.animateWithTextures(spriteArray, timePerFrame: 0.2)
let durationTime = SKAction.waitForDuration(0.1)
let repeatAction = SKAction.repeatActionForever(animationFrameAction)
let quenceAction = SKAction.sequence([durationTime, repeatAction])
animatedZoobieNode.runAction(quenceAction)
return animatedZoobieNode
}
Thanks very much my respectable brother Joseph Lord and Thank God i solved my problem by just dividing sprite kit atlas array count property by 2 because in this folder i had put both #2x and #3x images so when i used to get number of images from this atlas folder it used to return the number which was addition of #2x and #3x images.
In my game, I have a ball, walls and gaps between these walls. Ball and walls are SKSpriteNode, gap is SKNode. I use gap to count the score.
I have the following code to handle the contacts and collisions. I checked plenty of documents and tutorials but cant find why there are multiple gap contacts happen when my ball contacts the gap. It prints "gap contact" for 6 times every time.
Bodytypes definitions:
enum BodyType:UInt32 {
case ball = 1
case wall = 2
case gap = 4
}
Ball:
var ball = SKSpriteNode(texture: SKTexture(imageNamed: "img/ball.png"))
ball.position = CGPointMake(frame.width/2, 50)
ball.physicsBody = SKPhysicsBody(texture: ball.texture, size: ball.size)
ball.physicsBody?.dynamic = true
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.categoryBitMask = BodyType.ball.rawValue
ball.physicsBody?.collisionBitMask = BodyType.wall.rawValue
ball.physicsBody?.contactTestBitMask = BodyType.gap.rawValue | BodyType.wall.rawValue
Walls (walls are created through scheduledTimerWithTimeInterval every 2 seconds):
var wall1 = SKSpriteNode(texture: nil, color: UIColor.blackColor(), size: CGSizeMake(frame.size.width, 20))
var wall2 = SKSpriteNode(texture: nil, color: UIColor.blackColor(), size: CGSizeMake(frame.size.width, 20))
let gapSize = 70
let gapMargin = 50
let gapPoint = CGFloat(arc4random_uniform(UInt32(frame.size.width) - gapSize - (gapMargin*2)) + gapMargin)
wall1.position = CGPointMake(-wall1.size.width/2 + gapPoint, self.frame.size.height)
wall2.position = CGPointMake(wall1.size.width/2 + CGFloat(gapSize) + gapPoint, self.frame.size.height)
wall1.physicsBody = SKPhysicsBody(rectangleOfSize: wall1.size)
wall1.physicsBody?.dynamic = false
wall2.physicsBody = SKPhysicsBody(rectangleOfSize: wall2.size)
wall2.physicsBody?.dynamic = false
wall1.physicsBody?.categoryBitMask = BodyType.wall.rawValue
wall2.physicsBody?.categoryBitMask = BodyType.wall.rawValue
Gap Node:
var gap = SKNode()
gap.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(CGFloat(gapSize), wall1.size.height))
gap.position = CGPointMake(CGFloat(gapSize)/2 + gapPoint, self.frame.size.height)
gap.runAction(moveAndRemoveWall)
gap.physicsBody?.dynamic = false
gap.physicsBody?.collisionBitMask = 0
gap.physicsBody?.categoryBitMask = BodyType.gap.rawValue
Contact Handling:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(contactMask) {
case BodyType.ball.rawValue | BodyType.gap.rawValue:
println("gap contact")
default:
println("wall contact")
movingObjects.speed = 0
explosion(ball.position)
gameOver = 1
}
}
Wall collision works perfectly and prints "wall contact" only once when I hit the wall but I couldnt fix this multiple contact issue between the ball and gap.
UPDATE: The problem is with the ball's physicsbody. If I use circleOfRadius it works perfectly but then, this doesnt provide a great experience to the player as my ball shape is not a perfect circle so I want to use the texture to cover the physicsbody.