How can I reduce the opacity of the shadows in RealityKit? - swift

I composed a scene in Reality Composer and added 3 objects in it. The problem is that the shadows are too intense (dark).
I tried using the Directional Light in RealityKit from this answer rather than a default light from Reality Composer (since you don't have an option to adjust light in it).
Update
I implemented the spotlight Lighting as explained by #AndyFedo in the answer. The shadow is still so dark.

In case you need soft and semi-transparent shadows in your scene, use SpotLight lighting fixture which is available when you use a SpotLight class or implement HasSpotLight protocol. By default SpotLight is north-oriented. At the moment there's no opacity instance property for shadows in RealityKit.
outerAngleInDegrees instance property must be not more than 179 degrees.
import RealityKit
class Lighting: Entity, HasSpotLight {
required init() {
super.init()
self.light = SpotLightComponent(color: .yellow,
intensity: 50000,
innerAngleInDegrees: 90,
outerAngleInDegrees: 179, // greater angle – softer shadows
attenuationRadius: 10) // can't be Zero
}
}
Then create shadow instance:
class ViewController: NSViewController {
#IBOutlet var arView: ARView!
override func awakeFromNib() {
arView.environment.background = .color(.black)
let spotLight = Lighting().light
let shadow = Lighting().shadow
let boxAndCurlAnchor = try! Experience.loadBoxAndCurl()
boxAndCurlAnchor.components.set(shadow!)
boxAndCurlAnchor.components.set(spotLight)
arView.scene.anchors.append(boxAndCurlAnchor)
}
}
Here's an image produced without this line: boxAnchor.components.set(shadow!).
Here's an image produced with the following value outerAngleInDegrees = 140:
Here's an image produced with the following value outerAngleInDegrees = 179:
In a room keep SpotLight fixture at a height of 2...4 meters from a model.
For bigger objects you must use higher values for intensity and attenuationRadius:
self.light = SpotLightComponent(color: .white,
intensity: 625000,
innerAngleInDegrees: 10,
outerAngleInDegrees: 120,
attenuationRadius: 10000)
Also you can read my STORY about RealityKit lights on Medium.

The shadows appear darker when I use "Hide" action sequence on "Scene Start" and post a notification to call "Show" action sequence on tap gesture.
The shadows were fixed when I scaled the Object to 0% and post Notification to call "Move,Rotate,Scale to" action sequence on tap gesture.
Scaled Image
Unhide Image
Object Difference with hidden and scaled actions
import UIKit
import RealityKit
import ARKit
class Lighting: Entity, HasDirectionalLight {
required init() {
super.init()
self.light = DirectionalLightComponent(color: .red, intensity: 1000, isRealWorldProxy: true)
}
}
class SpotLight: Entity, HasSpotLight {
required init() {
super.init()
self.light = SpotLightComponent(color: .yellow,
intensity: 50000,
innerAngleInDegrees: 90,
outerAngleInDegrees: 179, // greater angle – softer shadows
attenuationRadius: 10) // can't be Zero
}
}
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
enum TapObjects {
case None
case HiddenChair
case ScaledChair
}
var furnitureAnchor : Furniture._Furniture!
var tapObjects : TapObjects = .None
override func viewDidLoad() {
super.viewDidLoad()
furnitureAnchor = try! Furniture.load_Furniture()
arView.scene.anchors.append(furnitureAnchor)
addTapGesture()
}
func addTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTap))
arView.addGestureRecognizer(tapGesture)
}
#objc func onTap(_ sender: UITapGestureRecognizer) {
switch tapObjects {
case .None:
furnitureAnchor.notifications.unhideChair.post()
tapObjects = .HiddenChair
case .HiddenChair:
furnitureAnchor.notifications.scaleChair.post()
tapObjects = .ScaledChair
default:
break
}
}
}

Related

Create border or outline on ModelEntity?

How can I create a border/outline on a ModelEntity in RealityKit?
Something like this blue border in Reality Composer:
You can achieve similar effect in two ways: either using Metal framework's features, or natively, in RealityKit (but sometimes with some visual artifacts). In RealityKit, such an outline could be rendered with faceCulling property for cloned model:
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
let scene = try! Experience2.loadScene()
let scene2 = scene.clone(recursive: true)
let outline = scene2.findEntity(named: "simpBld_root") as! ModelEntity
outline.scale *= 1.02
var material = PhysicallyBasedMaterial()
material.emissiveColor.color = .white
material.emissiveIntensity = 0.5
// an outer surface doesn't contribute to the final image
material.faceCulling = .front
outline.model?.materials[0] = material
arView.scene.anchors.append(scene)
arView.scene.anchors.append(scene2)
}
}
P. S.
In your case, the name of a rook is:
.findEntity(named: "chess_rook_white_base_iconic_lod0")

RealityKit – Which Entity is intersecting with other Entity

let height: Float = 1
let width: Float = 0.5
let box = MeshResource.generateBox(width: 0.02, height: height, depth: width)
This box will have a real-time position same as the current camera position, In AR World I would have multiple boxes with different shapes, I want to identify which object is intersecting with the current real-time box.
I can not do this with position matching (The nearest one). I literally want to know the object which is touching/intersecting the real-time box.
Thanks in advance.
You can easily do that using subscribe() method. The following code is a reference:
(physics for both objects was enabled in Reality Composer)
import UIKit
import RealityKit
import Combine
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
var subscriptions: [Cancellable] = []
override func viewDidLoad() {
super.viewDidLoad()
let boxScene = try! Experience.loadBox()
arView.scene.anchors.append(boxScene)
let floorEntity = boxScene.children[0].children[1]
let subscribe = arView.scene.subscribe(to: CollisionEvents.Began.self,
on: floorEntity) { (event) in
print("Collision Occured")
print(event.entityA.name)
print(event.entityB.name)
}
self.subscriptions += [subscribe]
}
}

RealityKit - Animate opacity of a ModelEntity?

By setting the color of a material on the model property of a ModelEntity, I can alter the opacity/alpha of an object. But how do you animate this? My goal is to animate objects with full opacity, then have them fade to a set opacity, such as 50%.
With SCNAction.fadeOpacity on a SCNNode in SceneKit, this was particularly easy.
let fade = SCNAction.fadeOpacity(by: 0.5, duration: 0.5)
node.runAction(fade)
An Entity conforms to HasTransform, but that will only allow you to animate scale, position, and orientation. Nothing to do with animation of the material for something like fading it in or out. The effect is in RealityComposer if you create a behavior for animating hide or showing, but there doesn't seem to be something similar to HasTransform to provide functionality for animating opacity.
I've been all around the documentation looking for something, my next idea is essentially creating a custom animation to replace this behavior, but it seems like it should be available and I am just not finding it.
I tested it using different techniques and came to the sad conclusion: you can't animate a material's opacity in RealityKit framework because RealityKit materials don't support animation at runtime (for now I hope). Let's wait for RealityKit's major update.
Here's a code you can use for test
(arView.alpha property just works):
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
arView.alpha = 1.0
opacityAnimation()
}
func opacityAnimation() {
UIView.animate(withDuration: 5.0,
animations: {
self.arView.alpha = 0.0
})
}
}
And use this code snippet in order to make sure that animation doesn't work properly
(there's no animation process, just value assignment):
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
let tetheringAnchor = AnchorEntity(world: [0,0,0])
var material = SimpleMaterial()
let mesh: MeshResource = .generateSphere(radius: 0.5)
var sphereComponent: ModelComponent? = nil
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
material.metallic = .float(1.0)
material.roughness = .float(0.0)
material.baseColor = .color(.red)
sphereComponent = ModelComponent(mesh: mesh,
materials: [material])
tetheringAnchor.components.set(sphereComponent!)
arView.scene.anchors.append(tetheringAnchor)
opacityAnimation()
}
func opacityAnimation() {
UIView.animate(withDuration: 5.0,
animations: {
self.material.metallic = .float(1.0)
self.material.roughness = .float(0.0)
self.material.baseColor = .color(.green)
self.sphereComponent = ModelComponent(mesh: self.mesh,
materials: [self.material])
self.tetheringAnchor.components.set(self.sphereComponent!)
self.arView.scene.anchors.append(self.tetheringAnchor)
})
}
}
As #AndyFedo says there is currently no way to animate the opacity nor alpha of an Entity.
Even changing a SimpleMaterial at run time currently results in flickering.
Having said this I was able to animate the Alpha of a SimpleMaterials Color, however based on testing it is in no way optimal or recommended for that matter.
But just in case you wanted to try to further experiment with this avenue please see an attached example which assumes that you only have a single SimpleMaterial:
class CustomBox: Entity, HasModel, HasAnchoring {
var timer: Timer?
var baseColour: UIColor!
//MARK:- Initialization
/// Initializes The Box With The Desired Colour
/// - Parameter color: UIColor
required init(color: UIColor) {
self.baseColour = color
super.init()
self.components[ModelComponent] = ModelComponent(mesh: .generateBox(size: [0.2, 0.2, 0.2]),
materials: [SimpleMaterial (color: baseColour, isMetallic: false)]
)
}
required init() { super.init() }
//MARK:- Example Fading
/// Fades The Colour Of The Entities Current Material
func fadeOut() {
var alpha: CGFloat = 1.0
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
if alpha == 0 {
timer.invalidate()
return
}
var material = SimpleMaterial()
alpha -= 0.01
material.baseColor = MaterialColorParameter.color(self.baseColour.withAlphaComponent(alpha))
material.metallic = .float(Float(alpha))
material.roughness = .float(Float(alpha))
DispatchQueue.main.async {
self.model?.materials = [material]
}
}
}
}
As such just to test you can create and then call the function like so:
let box = CustomBox(color: .green)
box.position = [0,0,-0.5]
arView.scene.anchors.append(box)
box.fadeOut()
Also I would politely ask, that this answer not get downvoted as I am simply iterating the fact that (a) it isn't possible with any current built in methods, and (b) that it can in part be achieved albeit to a very limited extent (and thus currently; in a way which one would see fit for production).
I don't know if it suits with your use case. But you should consider video material.
As you can see in this WWDC session (2min45). An entity with complex pulsating opacity.
https://developer.apple.com/videos/play/wwdc2020/10612/
you can also create the fade in experience in Reality Composer and trigger the .rcproject file in Xcode. Have not tested other interactions with .rcproject but I know at least this can load a model to fade in into the scene.

Dynamically change text of RealityKit entity

I have created a very simple scene ("SpeechScene") using Reality Composer, with a single speech callout object ("Speech Bubble") anchored to a Face anchor.
I have loaded this scene into code via the following:
let speechAnchor = try! Experience.loadSpeechScene()
arView.scene.anchors.append(speechAnchor)
let bubble = (arView.scene as? Experience.SpeechScene)?.speechBubble
It renders as expected. However, I would like to dynamically change the text of this existing entity.
I found a similar question here, but it's unclear to me how to refer to the meshResource property of a vanilla RealityKit.Entity object.
Is this possible? Thank you!
First Approach
At first you need to find out what's an hierarchy in Reality Composer's scene containing Bubble Speech object. For that I used simple print() command:
print(textAnchor.swift!.children[0].components.self) /* Bubble Plate */
print(textAnchor.swift!.children[1].components.self) /* Text Object */
Now I can extract a text entity object:
let textEntity: Entity = textAnchor.swift!.children[1].children[0].children[0]
And bubble plate entity object:
let bubbleEntity: Entity = textAnchor.swift!.children[0]
Here's a final code version that you can adapt for your needs:
import RealityKit
class GameViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
let textAnchor = try! SomeText.loadTextScene()
let textEntity: Entity = textAnchor.swift!.children[1].children[0].children[0]
textAnchor.swift!.parent!.scale = [4,4,4] // Scale for both objects
var textModelComp: ModelComponent = (textEntity.components[ModelComponent])!
var material = SimpleMaterial()
material.baseColor = .color(.red)
textModelComp.materials[0] = material
textModelComp.mesh = .generateText("Obj-C",
extrusionDepth: 0.01,
font: .systemFont(ofSize: 0.08),
containerFrame: CGRect(),
alignment: .left,
lineBreakMode: .byCharWrapping)
textEntity.position = [-0.1,-0.05, 0.01]
textAnchor.swift!.children[1].children[0].children[0].components.set(textModelComp)
arView.scene.anchors.append(textAnchor)
}
}
Second Approach
And you can always use a simpler approach for this case – to create several scenes in Reality Composer, each one must contain different speech-object.
Consider, this code isn't for tracking, it's just a test for dynamically switching two objects using Tap Gesture. Then you need to adapt this code for tracking faces.
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
var counter = 0
var bonjourObject: FaceExperience.Bonjour? = nil
var holaObject: FaceExperience.Hola? = nil
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reality Composer Scene named "Bonjour"
// Model name – "french"
bonjourObject = try! FaceExperience.loadBonjour()
bonjourObject?.french?.scale = SIMD3(x: 2, y: 2, z: 2)
bonjourObject?.french?.position.y = 0.25
// Reality Composer Scene named "Hola"
// Model name – "spanish"
holaObject = try! FaceExperience.loadHola()
holaObject?.spanish?.scale = SIMD3(x: 2, y: 2, z: 2)
holaObject?.spanish?.position.z = 0.3
}
#IBAction func tapped(_ sender: UITapGestureRecognizer) {
if (counter % 2) == 0 {
arView.scene.anchors.removeAll()
arView.scene.anchors.append(holaObject!)
} else {
arView.scene.anchors.removeAll()
arView.scene.anchors.append(bonjourObject!)
}
counter += 1
}
}
If you want a text portion to be on the same place – just copy-paste object from one scene to another.
#maxxfrazer is correct in his assertion that currently the only way to change text dynamically is to replace the ModelComponentof the Entity assuming of course it adheres to the HasModel Protocol.
I have written a simple extension which can help with this:
//-------------------------
//MARK: - Entity Extensions
//-------------------------
extension Entity{
/// Changes The Text Of An Entity
/// - Parameters:
/// - content: String
func setText(_ content: String){ self.components[ModelComponent] = self.generatedModelComponent(text: content) }
/// Generates A Model Component With The Specified Text
/// - Parameter text: String
func generatedModelComponent(text: String) -> ModelComponent{
let modelComponent: ModelComponent = ModelComponent(
mesh: .generateText(text, extrusionDepth: TextElements().extrusionDepth, font: TextElements().font,
containerFrame: .zero, alignment: .center, lineBreakMode: .byTruncatingTail),
materials: [SimpleMaterial(color: TextElements().colour, isMetallic: true)]
)
return modelComponent
}
}
//--------------------
//MARK:- Text Elements
//--------------------
/// The Base Setup Of The MeshResource
struct TextElements{
let initialText = "Cube"
let extrusionDepth: Float = 0.01
let font: MeshResource.Font = MeshResource.Font.systemFont(ofSize: 0.05, weight: .bold)
let colour: UIColor = .white
}
In order to use it lets say you create an Entity called textEntity:
var textEntity = Entity()
You can then set the dynamically change the Text via replacing the ModelComponent and setting the MeshResource at any time by simply calling the following method:
textEntity.setText("Stack Overflow")
Of course in regard to centering or aligning the text you will need to do some simple calculations (which I have ommited here).
Hope it helps.
Find your model entity (maybe by putting a breakpoint and looking through the children initially), find the Entity that conforms to the HasModel protocol, then replace its model with a different one using generatetext:
https://developer.apple.com/documentation/realitykit/meshresource/3244422-generatetext

In Swift’s UIKit Dynamics, how can I define a circle boundary to contain a UIView?

I have researched a LOT, but the only examples I can find anywhere are for the purpose of defining the bounds of a UIView so that they collide/bounce off each other on the OUTSIDE of the objects.
Example: A ball hits another ball and they bounce away from each other.
But what I want to do is create a circular view to CONTAIN other UIViews, such that the containing boundary is a circle, not the default square. Is there a way to achieve this?
Yes, that's totally possible. The key to achieving collision within a circle is to
Set the boundary for the collision behaviour to be a circle path (custom UIBezierPath) and
Set the animator’s referenceView to be the circle view.
Output:
Storyboard setup:
Below is the code of the view controller for the above Storyboard. The magic happens in the simulateGravityAndCollision method:
Full Xcode project
class ViewController: UIViewController {
#IBOutlet weak var redCircle: UIView!
#IBOutlet weak var whiteSquare: UIView!
var animator:UIDynamicAnimator!
override func viewDidLoad() {
super.viewDidLoad()
self.redCircle.setCornerRadius(self.redCircle.bounds.width / 2)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { [unowned self] in
self.simulateGravityAndCollision()
}
}
func simulateGravityAndCollision() {
//The dynamic animation happens only within the reference view, i.e., our red circle view
animator = UIDynamicAnimator.init(referenceView: self.redCircle)
//Only the inside white square will be affected by gravity
let gravityBehaviour = UIGravityBehavior.init(items: [self.whiteSquare])
//We also apply collision only to the white square
let collisionBehaviour = UICollisionBehavior.init(items:[self.whiteSquare])
//This is where we create the circle boundary from the redCircle view's bounds
collisionBehaviour.addBoundary(withIdentifier: "CircleBoundary" as NSCopying, for: UIBezierPath.init(ovalIn: self.redCircle.bounds))
animator.addBehavior(gravityBehaviour)
animator.addBehavior(collisionBehaviour)
}
}
extension UIView {
open override func awakeFromNib() {
super.awakeFromNib()
self.layer.allowsEdgeAntialiasing = true
}
func setCornerRadius(_ amount:CGFloat) {
self.layer.cornerRadius = amount
self.layer.masksToBounds = true
self.clipsToBounds = true
}
}