How to animate geometry using the primitiveRange property in SceneKit? - core-animation

I am using Apple's SceneKit to render a tube which consists of 50 triangle strips as illustrated here:
I want to animate a "growing" tube by using the primitiveRange property.
let tube = createTube() // create's tube.geometry which has 50 elements
let tubeNode = SCNNode(geometry: tube.geometry)
...
let animation = CABasicAnimation(keyPath: "geometry.elements[0].primitiveRange")
animation.fromValue = NSValue(range: NSRange(location: 0, length: 0))
animation.toValue = NSValue(range: NSRange(location: 0, length: tube.geometry.elements.count))
animation.duration = 5
animation.repeatCount = Float.infinity
tubeNode.geometry?.addAnimation(animation, forKey: "don't care")
scene.rootNode.addChildNode(tubeNode)
Nothing is animating (I simply see the full tube above) and I am not getting any warnings on the console. If I change the key "geometry.elements.primitiveRange" to "foo" I do get the following warning on the console
ExtrudedTube[98737:24354579] [SceneKit] Error: _C3DModelPathResolverRegistryResolvePathWithClassName unknown path (
foo
)
Perhaps this property is not animatable? I don't see documentation that says it is or isn't. Any idea how to create the "growing tube" animation using this mesh in SceneKit?

I was never able to pull off the animation using the "keypath" (I think there probably is a way to do it, but there is no documentation on the details). I can use SCNAction though it it works well:
if let numPrimitives = tubeNode.geometry?.elements[0].primitiveCount {
tubeNode.geometry?.elements[0].primitiveRange = NSRange(location: 0, length: 0)
let duration : Double = 5.0
let growTube = SCNAction.customAction(duration: duration, action: {node, elapsedTime in
let f = Double(elapsedTime)/duration
let n = Int(f*Double(numPrimitives))
node.geometry?.elements[0].primitiveRange = NSRange(location: 0, length: n)
})
tubeNode.runAction(growTube)
}

Related

Vulkan Instanced Rendering Weird Depth Buffer Behaviour

I am rendering a single mesh test object that contains 8000 cubes, 1 monkey, and 1 plane. I checked normals and winding orders. all seems correct for mesh.
if I render a single instance, the depth buffer works correctly. if I render using instanced when instant number increase depth buffer doesn't work correctly.
//Depth Attachment
//Create Depth Image
depthImage = new Image(
physicalDevice_,
logicalDevice_,
VK_IMAGE_TYPE_2D,
depthFormat,
VK_IMAGE_TILING_OPTIMAL,
swapChainExtent.width,
swapChainExtent.height,
1,
sampleCount_, //do we really need to sample depth????
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
depthImageView = new ImageView(
logicalDevice_,
VK_IMAGE_VIEW_TYPE_2D,
depthImage->handle,
depthFormat,
1,
VK_IMAGE_ASPECT_DEPTH_BIT);
//Render pass Depth Attachment
VkAttachmentDescription depthAttachment{};
depthAttachment.format = depthFormat;
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
//Graphics Pipeline
VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.minDepthBounds = 0.0f; // Optional
depthStencil.maxDepthBounds = 1.0f; // Optional
depthStencil.stencilTestEnable = VK_FALSE;
depthStencil.front = {}; // Optional
depthStencil.back = {}; // Optional
Below the screen is the view from the 100th instance of that mesh.
The higher the instance number, the more the depth buffer deteriorates.
The result is still the same if I use a different model.
What am I missing?
Result:
Depth Buffer:
For those who may struggle with similar issue. I found a solution.
Actually, this issue is not related to instancing directly but it's a bit confusing issue becomes visible as the instance index increases.
the cause is projection matrix z-near value was so small.
...
fieldOfView = 65.0f;
znear = 0.001f; // ! this much small value cause this problem
zfar = 5000.0f;
...
changed z-near to an acceptable value. Problem solved.
...
fieldOfView = 65.0f;
znear = 0.1f;
zfar = 5000.0f;
...

How do you get motion blur working in SceneKit?

According to WWDC 2017, motionBlurIntensity was added as a property to SCNCamera. I've tried the following and failed to get SceneKit to blur my scene when the camera is moved:
Set wantsHDR to true
Add SCNDisableWideGamut as a Boolean with the value of YES in every Info.plist in my Xcode project
Move a SCNBox by changing its SCNNode's position in front of the camera with motionBlurIntensity set to 1.0
Move the camera itself by changing its SCNNode's position with motionBlurIntensity set to 1.0
Animate the camera using an SCNTransaction with motionBlurIntensity set to 1.0 instead of changing its position each frame
Do the above with motionBlurIntensity set to 500 or greater
I run the following code every rendered frame like so:
camNode.position = SCNVector3Make(cx, cy, cz);
camNode.eulerAngles = SCNVector3Make(rotx, roty, rotz);
camNode.camera.wantsDepthOfField = enableDOF;
camNode.camera.wantsHDR = enableHDR;
camNode.camera.zNear = camNearVal;
camNode.camera.zFar = camFarVal;
camNode.camera.focalLength = camFocalLength;
camNode.camera.usesOrthographicProjection = usingOrthoProjection;
if(!usingOrthoProjection)
{
camNode.camera.projectionTransform = SCNMatrix4FromGLKMatrix4(GLKMatrix4MakeWithArray(projection));
}
else
{
// Ortho options
camNode.camera.orthographicScale = orthoScale;
if(cam_projectionDir == 1)
camNode.camera.projectionDirection = SCNCameraProjectionDirectionHorizontal;
else
camNode.camera.projectionDirection = SCNCameraProjectionDirectionVertical;
}
// DOF
camNode.camera.sensorHeight = dof_sensorHeight;
camNode.camera.focusDistance = dof_focusDistance;
camNode.camera.fStop = dof_fStop;
camNode.camera.apertureBladeCount = dof_apertureBladeCount;
camNode.camera.focalBlurSampleCount = dof_focalBlurSampleCount;
// Motion blur
camNode.camera.motionBlurIntensity = motionBlurIntensity;
And here is where the SCNRenderer sets its pointOfView to the camera:
mainRenderer.scene = mainScene;
mainRenderer.pointOfView = camNode;
id<MTLCommandBuffer> myCommandBuffer = [_commandQueue commandBuffer];
[mainRenderer updateAtTime: currentFrame];
[mainRenderer renderWithViewport:CGRectMake(0,0,(CGFloat)_viewWidth, (CGFloat)_viewHeight) commandBuffer:myCommandBuffer passDescriptor:_renderPassDescriptor];
[myCommandBuffer commit];
HDR effects like Bloom and SSAO work properly, just not motion blur.
I'm using Xcode Version 10.1 on macOS Mojave.
I ran the Badger sample app and the motion blur in that project works on my computer.
Am I missing something here? Any insight would be greatly appreciated.
The default value of a motionBlurIntensity property is 0.0. It results in no motion blur effect. Higher values (toward a maximum of 1.0) create more pronounced motion blur effects. Motion blur is not supported when wide-gamut color rendering is enabled. Wide-gamut rendering is enabled by default on supported devices; to opt out, set the SCNDisableWideGamut key in your app's Info.plist file.
Test it with my code:
let cameraNode1 = SCNNode()
cameraNode1.camera = SCNCamera()
cameraNode1.camera?.motionBlurIntensity = 3 // MOTION BLUR
scene.rootNode.addChildNode(cameraNode1)
let move = CABasicAnimation(keyPath: "translation")
move.fromValue = NSValue(scnVector3: SCNVector3(x: 7, y: -5, z: 0))
move.toValue = NSValue(scnVector3: SCNVector3(x: 7, y: 5, z: 0))
move.duration = 0.5
move.repeatCount = 20
move.autoreverses = true
move.repeatCount = .infinity
let sphereNode1 = SCNNode()
sphereNode1.geometry = SCNSphere(radius: 2)
sphereNode1.addAnimation(move, forKey: "back and forth")
scene.rootNode.addChildNode(sphereNode1)
Works perfectly.

TweenJS rect command "grow from center"

new to createjs here:
Created this fiddle: example. I would like the rect to grow from the center and not from the top left corner. I couldn't find any help, I don't know what to search for.
Code:
const PAGE_WIDTH = 150,
PAGE_HEIGHT = 75;
var stage = new createjs.Stage("canvas");
createjs.Ticker.setFPS(60);
createjs.Ticker.on("tick", tick);
var page = new createjs.Shape();
page.regX = PAGE_WIDTH/2;
page.regY = PAGE_HEIGHT/2;
page.x = 50;
page.y = 50;
stage.addChild(page);
var pageCommand = page.graphics
.beginFill('red')
.drawRect(0, 0, 1, 1).command;
createjs.Tween.get(pageCommand)
.to({w:PAGE_WIDTH, h: PAGE_HEIGHT}, 700, createjs.Ease.elasticOut)
function tick() {
stage.update();
}
Thanks.
The best way is to change the registration point of your graphics so it grows from the center.
Your code:
.drawRect(0, 0, 1, 1).command;
.to({w:PAGE_WIDTH, h: PAGE_HEIGHT})
Instead:
.drawRect(-1.-1, 2, 2);
.to({x:-PAGE_WIDTH/2, y:-PAGE_WIDTH/2, w:PAGE_WIDTH, h: PAGE_HEIGHT})
Fiddle: http://jsfiddle.net/1kjkqo2v/
[edit: I said 2 options initially, but the second (using regX and regY) wouldn't work well, since you have to tween that value as well as your size changes.]

merge two sprite nodes in sprite kit

I am designing a sprite kit game where I want to merge two sprite nodes (rect shapes) together and then act and move them as one sprite node ?
I've been searching lot and I didn't find results
Is that possible in sprite kit ? and how can I do it ?
I'll appreciate any help, Thanks
Add one node as a child of the other. If you set it's node2.physicsbody.dynamic=NO; it will move as a single node. There will be separate physicsbodies but you can set them to the same category bitmask.
The answer linked here may help with notation: How to detect contact on different areas of a physicsbody
If you have two sprite nodes, you simply do this :
[firstSprite addChild:secondSprite]
Here is an answer with which I have achieved what you are looking for using SpriteKit (Swift)
First apply Physics to your scene
Example:
self.physicsWorld.gravity = CGVectorMake(0, -9.8)
physicsWorld.contactDelegate = self
let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sceneBody.friction = 1
self.physicsBody = sceneBody
self.physicsBody?.categoryBitMask = PhysicsCategory.Boarder
self.physicsBody?.contactTestBitMask = PhysicsCategory.None
self.physicsBody?.collisionBitMask = PhysicsCategory.Item
Second, Apply Physics to the two skspritenode's that you want to join together
Example
func mySpritetie1() {
spritetie1 = SKSpriteNode(imageNamed: "img1")
spritetie1.name = kAnimalNodeName
spritetie1.position = CGPoint(x: 170, y: 35)
spritetie1.size = CGSizeMake(36, 100)
spritetie1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(22, 100))
spritetie1.physicsBody?.affectedByGravity = false
spritetie1.physicsBody?.categoryBitMask = PhysicsCategory.Item
spritetie1.physicsBody?.collisionBitMask = PhysicsCategory.Boarder
spritetie1.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
self.addChild(spritetie1)
}
func mySpritetie2() {
spritetie2 = SKSpriteNode(imageNamed: "img2")
spritetie2.name = kAnimalNodeName
spritetie2.position = CGPoint(x: 250, y: 60)
spritetie2.size = CGSizeMake(90, 79)
spritetie2.physicsBody = SKPhysicsBody(circleOfRadius: 25)
spritetie2.physicsBody?.affectedByGravity = false
spritetie2.physicsBody?.categoryBitMask = PhysicsCategory.Item
spritetie2.physicsBody?.collisionBitMask = PhysicsCategory.Boarder
spritetie2.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
self.addChild(spritetie2)
}
Third, now joining them
Example
let joint = SKPhysicsJointFixed.jointWithBodyA(spritetie1.physicsBody!, bodyB: spritetie2.physicsBody!,
anchor: CGPointMake(CGRectGetMidX(spritetie1.frame), CGRectGetMinY(spritetie2.frame)))
self.physicsWorld.addJoint(joint)
This will join the two body at the anchor point you give.
Now you can animate any one of that (spritetie1 or spritetie2) using SKAction and the other will animate as if they are one and the same joined at the anchor point you defined.
If you feel this answer helped please do approve it. Hope it will help someone out there.
Please use this two link to learn about all thats in my code:
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Physics/Physics.html
A very basic and good tutorial to learn about simulating physics https://www.youtube.com/playlist?list=PLa41xEuH5n_pwuNm7Z3L5qxDhVE0sOEqb
Thanks

KineticJS won't draw a dashed line in Internet Explorer 10

Trying to display a dashed line with KineticJS (v4.7.3). It works fine in Chrome, but in IE (v10), a normal solid line is displayed.
Here's the code:
var element = document.getElementById('target'),
stage = new Kinetic.Stage({
container: element,
width: element.offsetWidth,
height: element.offsetHeight
}),
layer = new Kinetic.Layer();
layer.add(new Kinetic.Line({
points: [10, 10, 190, 190],
stroke: 'black',
strokeWidth: 1,
dashArray: [5, 4]
}));
stage.add(layer);
And you can see the behavior for yourself in here.
Fixed in IE-11 !
Until all "bad" IE's die, you can "do-it-yourself" fairly easily for lines (less easily for curves).
You can use a custom Kinetic.Shape which gives you access to a canvas context (a wrapped context).
Code is taken from this SO post: dotted stroke in <canvas>
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP && CP.lineTo){
CP.dashedLine = function(x,y,x2,y2,dashArray){
if (!dashArray) dashArray=[10,5];
if (dashLength==0) dashLength = 0.001; // Hack for Safari
var dashCount = dashArray.length;
this.moveTo(x, y);
var dx = (x2-x), dy = (y2-y);
var slope = dx ? dy/dx : 1e15;
var distRemaining = Math.sqrt( dx*dx + dy*dy );
var dashIndex=0, draw=true;
while (distRemaining>=0.1){
var dashLength = dashArray[dashIndex++%dashCount];
if (dashLength > distRemaining) dashLength = distRemaining;
var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
if (dx<0) xStep = -xStep;
x += xStep
y += slope*xStep;
this[draw ? 'lineTo' : 'moveTo'](x,y);
distRemaining -= dashLength;
draw = !draw;
}
}
}
Totally off-topic: Go Wisconsin! I spent many a great summer at my grandmothers house in
Lacrosse.
Short answer: Not supported.
Via a thread here, the creator of KineticJS addresses this issue:
"[T]he dashArray property now uses the browser-implemented dashArray
property of the canvas context, according to the w3c spec. Firefox is
a little behind at the moment."
If you follow the thread, you'll discover that this was an issue for Firefox at one point, too, but that has been resolved. However, support for IE should apparently not be expected any time soon.