js/SceneKit/SCNNode.js
'use strict'
import CAAnimationGroup from '../QuartzCore/CAAnimationGroup'
import CABasicAnimation from '../QuartzCore/CABasicAnimation'
import CAMediaTimingFunction from '../QuartzCore/CAMediaTimingFunction'
import CAKeyframeAnimation from '../QuartzCore/CAKeyframeAnimation'
import NSObject from '../ObjectiveC/NSObject'
//import SCNActionable from './SCNActionable'
//import SCNAnimatable from './SCNAnimatable'
//import SCNBoundingVolume from './SCNBoundingVolume'
//import SCNGeometry from './SCNGeometry'
//import SCNGeometrySource from './SCNGeometrySource'
//import SCNLight from './SCNLight'
//import SCNCamera from './SCNCamera'
//import SCNMorpher from './SCNMorpher'
//import SCNSkinner from './SCNSkinner'
import SCNMatrix4 from './SCNMatrix4'
//import SCNMatrix4MakeScale from './SCNMatrix4MakeScale'
import SCNMatrix4MakeTranslation from './SCNMatrix4MakeTranslation'
import SCNVector3 from './SCNVector3'
import SCNVector4 from './SCNVector4'
//import SCNQuaternion from './SCNQuaternion'
//import SCNConstraint from './SCNConstraint'
import SCNMovabilityHint from './SCNMovabilityHint'
//import SCNNodeRendererDelegate from './SCNNodeRendererDelegate'
import SCNOrderedDictionary from './SCNOrderedDictionary'
//import SCNParticleSystem from './SCNParticleSystem'
//import SCNPhysicsBody from './SCNPhysicsBody'
//import SCNPhysicsField from './SCNPhysicsField'
import SCNPhysicsWorld from './SCNPhysicsWorld'
import SCNTransaction from './SCNTransaction'
//import SCNAudioPlayer from './SCNAudioPlayer'
//import SCNHitTestResult from './SCNHitTestResult'
import SKColor from '../SpriteKit/SKColor'
import * as Constants from '../constants'
import _InstanceOf from '../util/_InstanceOf'
const _localFront = new SCNVector3(0, 0, 1)
const _localRight = new SCNVector3(1, 0, 0)
const _localUp = new SCNVector3(0, 1, 0)
/**
* A structural element of a scene graph, representing a position and transform in a 3D coordinate space, to which you can attach geometry, lights, cameras, or other displayable content.
* @access public
* @extends {NSObject}
* @implements {SCNActionable}
* @implements {SCNAnimatable}
* @implements {SCNBoundingVolume}
* @see https://developer.apple.com/documentation/scenekit/scnnode
*/
export default class SCNNode extends NSObject {
static get _propTypes() {
return {
name: 'string',
light: 'SCNLight',
camera: 'SCNCamera',
geometry: ['SCNGeometry', (obj, value) => {
obj.geometry = value
obj.boundingBox = value.boundingBox
}],
morpher: 'SCNMorpher',
skinner: 'SCNSkinner',
categoryBitMask: 'integer',
paused: ['boolean', 'isPaused'],
position: ['SCNVector3', '_position'],
rotation: ['SCNVector4', '_rotation'],
orientation: ['SCNVector4', (obj, value) => {
obj.orientation = value
}],
scale: ['SCNVector3', '_scale'],
hidden: ['boolean', 'isHidden'],
opacity: ['float', '_opacity'],
renderingOrder: 'integer',
castsShadow: 'boolean',
childNodes: ['NSArray', (obj, childNodes) => {
childNodes.forEach((child) => {
obj.addChildNode(child)
})
}],
physicsBody: ['SCNPhysicsBody', (obj, body) => {
obj.physicsBody = body
}],
physicsField: 'SCNPhysicsField',
particleSystem: ['NSArray', '_particleSystems'],
animations: ['NSMutableDictionary', (obj, anims) => {
this._loadAnimationArray(obj, anims)
obj._setAnimationsToPlayers()
}],
'animation-keys': ['NSMutableArray', (obj, keys) => {
obj._animationPlayers._keys = keys
}],
'animation-players': ['NSMutableArray', (obj, players) => {
obj._animationPlayers._values = players
obj._setAnimationsToPlayers()
}],
'action-keys': ['NSArray', null],
actions: ['NSMutableDictionary', (obj, acts) => {
this._loadActionArray(obj, acts)
}],
eulerAngles: ['SCNVector3', (obj, value) => {
obj.eulerAngles = value
}],
movabilityHint: 'integer',
clientAttributes: ['NSMutableDictionary', null],
nodeID: ['string', '_nodeID'],
entityID: ['string', '_entityID']
}
}
// Creating a Node
/**
* Creates and returns a node object with the specified geometry attached.
* @access public
* @constructor
* @param {?SCNGeometry} [geometry = null] - The geometry to be attached.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408020-init
*/
constructor(geometry = null) {
super()
// Managing Node Attributes
/**
* A name associated with the node.
* @type {?string}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408014-name
*/
this.name = null
/**
* The light attached to the node.
* @type {?SCNLight}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408004-light
*/
this.light = null
/**
* The camera attached to the node.
* @type {?SCNCamera}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407976-camera
*/
this.camera = null
/**
* The geometry attached to the node.
* @type {?SCNGeometry}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407966-geometry
*/
this._geometry = geometry
/**
* The morpher object responsible for blending the node’s geometry.
* @type {?SCNMorpher}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408022-morpher
*/
this.morpher = null
/**
* The skinner object responsible for skeletal animations of node’s contents.
* @type {?SCNSkinner}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407953-skinner
*/
this.skinner = null
/**
* A mask that defines which categories the node belongs to.
* @type {number}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407994-categorybitmask
*/
this.categoryBitMask = 0
// Working With Node Animation
/**
* A Boolean value that determines whether to run actions and animations attached to the node and its child nodes.
* @type {boolean}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407962-ispaused
*/
this.isPaused = false
/**
* A node object representing the state of the node as it currently appears onscreen.
* @type {SCNNode}
* @access private
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408030-presentation
*/
this._presentation = null
/**
*
* @type {boolean}
* @access private
*/
this._isPresentationInstance = false
// Managing the Node’s Transformation
/**
* The transformation applied to the node relative to its parent. Animatable.
* @type {SCNMatrix4}
* @access private
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407964-transform
*/
this._transform = new SCNMatrix4()
this._worldTransform = new SCNMatrix4()
/**
*
* @type {boolean}
* @access private
*/
this._transformUpToDate = false
/**
* The translation applied to the node. Animatable.
* @type {SCNVector3}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408026-position
*/
this._position = new SCNVector3(0, 0, 0)
/**
* The node’s orientation, expressed as a rotation angle about an axis. Animatable.
* @type {SCNVector4}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408034-rotation
*/
this._rotation = new SCNVector4(1, 0, 0, 0)
/**
* The node’s orientation, expressed as pitch, yaw, and roll angles, each in radians. Animatable.
* @type {SCNVector3}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407980-eulerangles
*/
//this.eulerAngles = null
/**
* The node’s orientation, expressed as a quaternion. Animatable.
* @type {SCNQuaternion}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408048-orientation
*/
//this.orientation = null
/**
* The scale factor applied to the node. Animatable.
* @type {SCNVector3}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408050-scale
*/
this._scale = new SCNVector3(1, 1, 1)
/**
* The pivot point for the node’s position, rotation, and scale. Animatable.
* @type {SCNMatrix4}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408044-pivot
*/
this.pivot = null
/**
* A list of constraints affecting the node’s transformation.
* @type {?SCNConstraint[]}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408016-constraints
*/
this.constraints = null
//this._worldTransform = null
// Modifying the Node Visibility
/**
* A Boolean value that determines the visibility of the node’s contents. Animatable.
* @type {boolean}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407967-ishidden
*/
this.isHidden = false
/**
* The opacity value of the node. Animatable.
* @type {number}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408010-opacity
*/
this._opacity = 1
this._worldOpacity = 1
/**
* The order the node’s content is drawn in relative to that of other nodes.
* @type {number}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407978-renderingorder
*/
this.renderingOrder = 0
/**
* A Boolean value that determines whether SceneKit renders the node’s contents into shadow maps.
* @type {boolean}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407955-castsshadow
*/
this.castsShadow = false
/**
* A value that indicates how SceneKit should handle the node when rendering movement-related effects.
* @type {SCNMovabilityHint}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1690499-movabilityhint
*/
this.movabilityHint = SCNMovabilityHint.fixed
// Managing the Node Hierarchy
/**
* The node’s parent in the scene graph hierarchy.
* @type {?SCNNode}
* @access private
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407968-parent
*/
this._parent = null
/**
* An array of the node’s children in the scene graph hierarchy.
* @type {SCNNode[]}
* @access private
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407984-childnodes
*/
this._childNodes = []
// Customizing Node Rendering
/**
* An array of Core Image filters to be applied to the rendered contents of the node.
* @type {?CIFilter[]}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407949-filters
*/
this.filters = null
/**
* An object responsible for rendering custom contents for the node using Metal or OpenGL.
* @type {?SCNNodeRendererDelegate}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408012-rendererdelegate
*/
this.rendererDelegate = null
// Adding Physics to a Node
/**
* The physics body associated with the node.
* @type {?SCNPhysicsBody}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407988-physicsbody
*/
this._physicsBody = null
/**
* The physics field associated with the node.
* @type {?SCNPhysicsField}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408006-physicsfield
*/
this.physicsField = null
// Working With Particle Systems
this._particleSystems = null
// Working With Positional Audio
this._audioPlayers = []
/**
*
* @type {?GKEntity}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2873004-entity
*/
this.entity = null
/**
*
* @type {SCNNodeFocusBehavior}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2881853-focusbehavior
*/
this.focusBehavior = null
///////////////////
// SCNActionable //
///////////////////
// Inspecting a Node’s Running Action
//this._hasActions = false
/**
* @access private
* @type {Map}
*/
this._actions = new Map()
///////////////////
// SCNAnimatable //
///////////////////
/**
* @access private
* @type {SCNOrderedDictionary}
*/
this._animations = new SCNOrderedDictionary()
/**
* @access private
* @type {SCNOrderedDictionary}
*/
this._animationPlayers = new SCNOrderedDictionary()
///////////////////////
// SCNBoundingVolume //
///////////////////////
// Working with Bounding Volumes
/**
* The minimum and maximum corner points of the object’s bounding box.
* @type {{min: SCNVector3, max: SCNVector3}}
* @see https://developer.apple.com/documentation/scenekit/scnboundingvolume/2034705-boundingbox
*/
this._boundingBox = null
this._fixedBoundingBox = null
//this._boundingSphere = null
/**
* @access private
* @type {?string}
*/
this._entityID = null
/**
* @access private
* @type {?string}
*/
this._nodeID = null
this._updateBoundingBox()
/**
* @access private
* @type {Promise}
*/
this._loadedPromise = null
}
static _loadAnimationArray(node, animations) {
//console.log('_loadAnimationArray start')
for(const animName of Object.keys(animations)){
const data = animations[animName]
const animation = this._loadAnimationData(data, animName)
node.addAnimationForKey(animation, animName)
}
//console.log('_loadAnimationArray done')
}
static _loadAnimationData(data, key) {
//console.log(`_loadAnimationData ${key} start`)
if(data.class === 'group'){
return this._loadAnimationGroup(data)
}else if(data.class === 'keyframe'){
return this._loadKeyframeAnimation(data.animation, key)
}else if(data.class === 'basic'){
const keyPath = data.keyPath || key
return this._loadBasicAnimation(data.animation, keyPath)
}else if(data.type === 'keyframedAnimation'){
return this._loadKeyframeAnimation(data, key)
}
//console.error(`unknown animation class: ${data.class}, type: ${data.type}, key: ${key}`)
throw new Error(`unknown animation class: ${data.class}, type: ${data.type}, key: ${key}`)
}
static _loadAnimationGroup(animation) {
//console.log('_loadAnimationGroup start')
const group = new CAAnimationGroup()
const data = animation.animation
group.isRemovedOnCompletion = Boolean(animation.removeOnCompletion)
// group.timingFunction
// group.delegate
group.usesSceneTimeBase = Boolean(animation.usesSceneTimeBase)
group.fadeInDuration = data.fadeInDuration
group.fadeOutDuration = data.fadeOutDuration
group.beginTime = data.beginTime
group.timeOffset = data.timeOffset
group.repeatCount = data.repeatCount
// group.repeatDuration
group.duration = data.duration
group.speed = data.speed
group.autoreverses = data.autoreverses
const fillMode = [
Constants.kCAFillModeRemoved,
Constants.kCAFillModeForwards,
Constants.kCAFillModeBackwards,
Constants.kCAFillModeBoth
]
group.fillMode = fillMode[data.fillModeMask]
// data.cumulative
// data.additive
// data.attributes
data.channels.forEach((channel) => {
const keyPath = channel.targetPath.join('.')
//console.error(`SCNNode animation group keyPath: ${keyPath}`)
const chAnim = this._loadAnimationData(channel.animation, keyPath)
group.animations.push(chAnim)
})
//console.log('_loadAnimationGroup done')
return group
}
static _loadKeyframeAnimation(data, keyPath) {
//console.log(`_loadKeyframeAnimation ${keyPath} start`)
const anim = new CAKeyframeAnimation(keyPath)
anim.isRemovedOnCompletion = Boolean(data.removeOnCompletion)
// anim.timingFunction
// anim.delegate
anim.usesSceneTimeBase = Boolean(data.sceneTimeBased)
anim.fadeInDuration = data.fadeInDuration
anim.fadeOutDuration = data.fadeOutDuration
anim.beginTime = data.beginTime
anim.timeOffset = data.timeOffset
anim.repeatCount = data.repeatCount
// anim.repeatDuration
anim.duration = data.duration
anim.speed = data.speed
anim.autoreverses = data.autoreverses
const fillMode = [
Constants.kCAFillModeRemoved,
Constants.kCAFillModeForwards,
Constants.kCAFillModeBackwards,
Constants.kCAFillModeBoth
]
anim.fillMode = fillMode[data.fillModeMask]
anim.isCumulative = Boolean(data.cumulative)
anim.isAdditive = Boolean(data.additive)
// data.attributes
const keyframe = data.keyframeController
anim.values = this._loadData(keyframe, 'values')
//anim.path
anim.keyTimes = this._loadData(keyframe, 'keytimes')
switch(keyframe.interpolationMode){
case 0:
default:
//anim.timingFunctions =
break
}
anim.keyTimes = anim.keyTimes.map((keyTime) => { return keyTime / anim.duration })
const calculationModes = [
Constants.kCAAnimationLinear,
Constants.kCAAnimationDiscrete,
Constants.kCAAnimationPaced,
Constants.kCAAnimationCubic,
Constants.kCAAnimationCubicPaced
]
anim.calculationMode = calculationModes[keyframe.calculationMode]
//anim.rotationMode
//anim.tensionValues
//anim.continuityValues
//anim.biasValues
//console.log(`_loadKeyframeAnimation ${keyPath} done`)
return anim
}
static _loadBasicAnimation(data, keyPath) {
//console.log(`_loadBasicAnimation ${keyPath} start`)
const anim = new CABasicAnimation(keyPath)
anim.isRemovedOnCompletion = Boolean(data.removeOnCompletion)
anim.timingFunction = new CAMediaTimingFunction(
data.timingFunction.c0,
data.timingFunction.c1,
data.timingFunction.c2,
data.timingFunction.c3
)
// anim.delegate
anim.usesSceneTimeBase = Boolean(data.sceneTimeBased)
anim.fadeInDuration = data.fadeInDuration
anim.fadeOutDuration = data.fadeOutDuration
anim.beginTime = data.beginTime
anim.timeOffset = data.timeOffset
anim.repeatCount = data.repeatCount
// anim.repeatDuration
anim.duration = data.duration
anim.speed = data.speed
anim.autoreverses = data.autoreverses
const fillMode = [
Constants.kCAFillModeRemoved,
Constants.kCAFillModeForwards,
Constants.kCAFillModeBackwards,
Constants.kCAFillModeBoth
]
anim.fillMode = fillMode[data.fillModeMask]
anim.isCumulative = Boolean(data.cumulative)
anim.isAdditive = Boolean(data.additive)
// data.attributes
// data.baseType
//console.log(`_loadBasicAnimation ${keyPath} done`)
return anim
}
static _loadActionArray(node, actions) {
//console.log('_loadActionArray start')
for(const actName of Object.keys(actions)){
const data = actions[actName]
//const action = this._loadActionData(data, actName)
//node.runActionForKey(action, actName)
node.runActionForKey(data, actName)
}
//console.log('_loadAnimationArray done')
}
//static _loadActionData(data, key) {
// console.log(`_loadActionData ${key} start`)
//}
static _loadData(data, key) {
//console.log(`_loadData ${key} start`)
const accessor = data[key].accessor
const components = accessor.componentsPerValue
const stride = accessor.stride
const offset = accessor.offset
const typeId = accessor.sourceTypeID
const padding = accessor.padding
const count = accessor.valuesCount
const sourceKey = `${key}-data`
const source = data[sourceKey]
const result = []
let pos = offset
if(accessor.componentsType === 1){
// float
for(let i=0; i<count; i++){
result.push(source.readFloatBE(pos))
pos += stride
}
}else if(accessor.componentsType === 6){
// double
for(let i=0; i<count; i++){
result.push(source.readDoubleBE(pos))
pos += stride
}
}else if(accessor.componentsType === 9){
// SCNVector3
for(let i=0; i<count; i++){
result.push(SCNVector3._initWithData(source, pos, true))
pos += stride
}
}else if(accessor.componentsType === 10){
// SCNVector4
for(let i=0; i<count; i++){
result.push(SCNVector4._initWithData(source, pos, true))
pos += stride
}
}else if(accessor.componentsType === 11){
// SCNMatrix4
for(let i=0; i<count; i++){
result.push(SCNMatrix4._initWithData(source, pos, true))
pos += stride
}
}else if(accessor.componentsType === 13){
// SKColor
for(let i=0; i<count; i++){
result.push(SKColor._initWithData(source, pos, true))
pos += stride
}
}else{
console.error(`unknown accessor componentsType: ${accessor.componentsType}`)
}
//console.log(`_loadData ${key} done`)
return result
}
/**
* Constructor for JSExport compatibility
* @access public
* @returns {SCNNode} -
*/
static node() {
return new SCNNode()
}
/**
* Constructor for JSExport compatibility
* @access public
* @param {?SCNGeometry} [geometry] - The geometry to be attached.
* @returns {SCNNode} -
*/
static nodeWithGeometry(geometry) {
return new SCNNode(geometry)
}
// Managing Node Attributes
get geometry() {
return this._geometry
}
set geometry(newValue) {
this._geometry = newValue
this._updateBoundingBox()
}
// Working With Node Animation
/**
* A node object representing the state of the node as it currently appears onscreen.
* @type {SCNNode}
* @desc When you use implicit animation (see SCNTransaction) to change a node’s properties, those node properties are set immediately to their target values, even though the animated node content appears to transition from the old property values to the new. During the animation SceneKit maintains a copy of the node, called the presentation node, whose properties reflect the transitory values determined by any in-flight animations currently affecting the node. The presentation node’s properties provide a close approximation to the version of the node that is currently displayed. SceneKit also uses the presentation node when computing the results of explicit animations, physics, and constraints.Do not modify the properties of the presentation node. (Attempting to do so results in undefined behavior.) Instead, you use the presentation node to read current animation values—for example, to create a new animation starting at those values. The presentation node has no parent or child nodes. To access animated properties of related nodes, use the node’s own parent and childNodes properties and the presentation property of each related node.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408030-presentation
*/
get presentation() {
if(this._presentation === null && !this._isPresentationInstance){
this._createPresentation()
}
return this._presentation
}
_createPresentation() {
if(this._isPresentationInstance){
return
}else if(this._presentation){
return
}
let p = this.copy()
p._isPresentationInstance = true
if(this.geometry !== null){
p.geometry = this.geometry.copy()
p.geometry._isPresentationInstance = true
p.geometry._geometryElements = []
this.geometry._geometryElements.forEach((element) => {
p.geometry._geometryElements.push(element.copy())
})
p.geometry._geometrySources = []
this.geometry._geometrySources.forEach((source) => {
p.geometry._geometrySources.push(source.copy())
})
this.geometry._presentation = p.geometry
}
if(this._particleSystems){
p._particleSystems = []
for(const system of this._particleSystems){
const pSystem = system._createPresentation()
p._particleSystems.push(pSystem)
}
}
this._presentation = p
}
// Managing the Node’s Transformation
/**
* The transformation applied to the node relative to its parent. Animatable.
* @type {SCNMatrix4}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407964-transform
*/
get transform() {
// FIXME: it should return the copy of _transform,
// but you should be able to change value with this statement:
// let node = new SCNNode()
// node.transform.m14 = 123
// console.log(node.transform.m14) // '123'
if(!this._transformUpToDate){
this._updateTransform()
}
return this._transform
}
set transform(newValue) {
this._transform = newValue
this._position = this._transform.getTranslation()
this._rotation = this._transform.getRotation()
this._scale = this._transform.getScale()
this._transformUpToDate = true
}
/**
* The world transform applied to the node.
* @type {SCNMatrix4}
* @desc A world transform is the node’s coordinate space transformation relative to the scene’s coordinate space. This transformation is the concatenation of the node’s transform property with that of its parent node, the parent’s parent, and so on up to the rootNode object of the scene.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407970-worldtransform
*/
get worldTransform() {
/*
if(this._parent === null){
if(this._isPresentationInstance){
return this._worldTransform
}
return this.transform
}
return this.transform.mult(this._parent.worldTransform)
*/
return this._worldTransform
}
_updateWorldTransform() {
let p = null
if(this._parent === null){
p = SCNMatrix4MakeTranslation(0, 0, 0)
}else{
p = this._parent._worldTransform
}
this._worldTransform = this.transform.mult(p)
if(this._presentation){
let pp = null
let ppOpacity = 1.0
if(this._parent === null){
pp = SCNMatrix4MakeTranslation(0, 0, 0)
}else if(this._parent._presentation === null){
pp = this._parent._worldTransform
ppOpacity = this._parent._worldOpacity
}else{
pp = this._parent._presentation._worldTransform
ppOpacity = this._parent._presentation._worldOpacity
}
this._presentation._updateTransform()
this._presentation._worldTransform = this._presentation.transform.mult(pp)
this._presentation._worldOpacity = this._presentation._opacity * ppOpacity
}
this._childNodes.forEach((child) => {
child._updateWorldTransform()
})
}
/*
_updatePresentationTransform() {
let p = null
if(this._parent === null){
p = SCNMatrix4MakeTranslation(0, 0, 0)
}else{
p = this._parent._presentation._worldTransform
}
this._presentation._worldTransform = this._presentation.transform.mult(parentTransform)
this._childNodes.forEach((child) => {
child._updatePrsentationTransform(this._presentation._worldTransform)
})
}
*/
/**
* The translation applied to the node. Animatable.
* @type {SCNVector3}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408026-position
*/
get position() {
return this._position
}
set position(newValue) {
if(typeof newValue.x !== 'number'
|| typeof newValue.y !== 'number'
|| typeof newValue.z !== 'number'){
throw new Error('error: SCNNode.position must have x, y, z values')
}
this._position.x = newValue.x
this._position.y = newValue.y
this._position.z = newValue.z
this._transformUpToDate = false
this._updateWorldTransform()
}
get rotation() {
return this._rotation
}
set rotation(newValue) {
if(typeof newValue.x !== 'number'
|| typeof newValue.y !== 'number'
|| typeof newValue.z !== 'number'
|| typeof newValue.w !== 'number'){
throw new Error('error: SCNNode.rotation must have x, y, z, w values')
}
const oldValue = this._rotation._copy()
this._rotation.x = newValue.x
this._rotation.y = newValue.y
this._rotation.z = newValue.z
this._rotation.w = newValue.w
this._transformUpToDate = false
this._updateWorldTransform()
SCNTransaction._addChange(this, 'rotation', oldValue, newValue)
}
get scale() {
return this._scale
}
set scale(newValue) {
if(typeof newValue.x !== 'number'
|| typeof newValue.y !== 'number'
|| typeof newValue.z !== 'number'){
throw new Error('error: SCNNode.scale must have x, y, z values')
}
this._scale.x = newValue.x
this._scale.y = newValue.y
this._scale.z = newValue.z
this._transformUpToDate = false
this._updateWorldTransform()
}
/**
* The node’s orientation, expressed as pitch, yaw, and roll angles, each in radians. Animatable.
* @type {SCNVector3}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407980-eulerangles
*/
get eulerAngles() {
/*
const rot = this._rotation
const euler = new SCNVector3()
const sinW = Math.sin(rot.w)
const cosWR = 1.0 - Math.cos(rot.w)
const len2 = rot.x * rot.x + rot.y * rot.y + rot.z * rot.z
if(len2 === 0){
return euler
}
const r = 1.0 / Math.sqrt(len2)
const x = rot.x * r
const y = rot.y * r
const z = rot.z * r
const s = y * sinW - x * z * cosWR
if(s > 0.998){
// TODO: check SceneKit implementation
euler.x = 0
euler.y = -Math.PI * 0.5
euler.z = -2.0 * Math.atan2(z * Math.sin(rot.w * 0.5), Math.cos(rot.w * 0.5))
}else if(s < -0.998){
// TODO: check SceneKit implementation
euler.x = 0
euler.y = Math.PI * 0.5
euler.z = 2.0 * Math.atan2(z * Math.sin(rot.w * 0.5), Math.cos(rot.w * 0.5))
}else{
euler.x = Math.atan2(x * sinW + y * z * cosWR, 1 - (y * y + x * x) * cosWR)
euler.y = Math.asin(s)
euler.z = Math.atan2(z * sinW + x * y * cosWR, 1 - (z * z + y * y) * cosWR)
}
return euler
*/
return this._rotation.rotationToEulerAngles()
}
set eulerAngles(newValue) {
this._rotation = newValue.eulerAnglesToRotation()
this._transformUpToDate = false
}
/**
* The node’s orientation, expressed as a quaternion. Animatable.
* @type {SCNQuaternion}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408048-orientation
*/
get orientation() {
return this._rotation.rotationToQuat()
}
set orientation(newValue) {
if(!_InstanceOf(newValue, SCNVector4)){
throw new Error('orientation must be SCNVector4')
}
this._rotation = newValue.quatToRotation()
this._transformUpToDate = false
}
/**
* @access private
* @returns {SCNVector4} -
*/
get _presentationWorldOrientation() {
if(this._parent === null){
return this.presentation.orientation
}
return this._parent._presentationWorldOrientation.cross(this.presentation.orientation)
}
/**
* @access private
* @returns {SCNVector4} -
*/
get _worldOrientation() {
if(this._parent === null){
return this.orientation
}
return this._parent._worldOrientation.cross(this.orientation)
}
/**
* @access private
* @returns {SCNVector4} -
*/
get _worldRotation() {
return this._worldOrientation.quatToRotation()
}
/**
* @access private
* @returns {SCNVector3} -
*/
get _presentationWorldTranslation() {
return this.presentation.worldTransform.getTranslation()
}
/**
* @access private
* @returns {SCNVector3} -
*/
get _worldTranslation() {
return this.worldTransform.getTranslation()
}
get worldPosition() {
return this.worldTransform.getTranslation()
}
set worldPosition(newValue) {
let parentTransform = null
if(this._parent === null){
parentTransform = SCNMatrix4MakeTranslation(0, 0, 0)
}else{
parentTransform = this._parent.worldTransform
}
const transform = SCNMatrix4MakeTranslation(newValue.x, newValue.y, newValue.z)
const inv = parentTransform.invert()
const newTransform = transform.mult(inv)
this._transform.m41 = newTransform.m41
this._transform.m42 = newTransform.m42
this._transform.m43 = newTransform.m43
this.transform = this._transform
}
/**
* @access private
* @returns {SCNVector3} -
*/
get _worldScale() {
}
/**
* The opacity value of the node. Animatable.
* @type {number}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408010-opacity
*/
get opacity() {
return this._opacity
}
set opacity(newValue) {
const oldValue = this._opacity
this._opacity = newValue
SCNTransaction._addChange(this, '_opacity', oldValue, newValue)
}
// Managing the Node Hierarchy
/**
* Adds a node to the node’s array of children.
* @access public
* @param {SCNNode} child - The node to be added.
* @returns {void}
* @desc Calling this method appends the node to the end of the childNodes array.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407974-addchildnode
*/
addChildNode(child) {
if(this._childNodes.indexOf(child) >= 0){
return
}
child.removeFromParentNode()
this._childNodes.push(child)
child._parent = this
child._resetPhysicsTransformRecursively(true)
}
/**
* Adds a node to the node’s array of children at a specified index.
* @access public
* @param {SCNNode} child - The node to be inserted.ImportantRaises an exception (invalidArgumentException) if child is nil.
* @param {number} index - The position at which to insert the new child node.ImportantRaises an exception (rangeException) if index is greater than the number of elements in the node’s childNodes array.
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407958-insertchildnode
*/
insertChildNodeAt(child, index) {
if(this._childNodes.indexOf(child) >= 0){
return
}
child.removeFromParentNode()
this._insertObjectInChildNodesAtIndex(child, index)
this._parent = this
}
/**
* Removes the node from its parent’s array of child nodes.
* @access public
* @returns {void}
* @desc Removing nodes from the node hierarchy serves two purposes. Nodes own their contents (child nodes or attached lights, geometries, and other objects), so deallocating unneeded nodes can reduce memory usage. Additionally, SceneKit does more work at rendering time with a large, complex node hierarchy, so removing nodes whose contents you don’t need to display can improve rendering performance.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407991-removefromparentnode
*/
removeFromParentNode() {
const parentNode = this._parent
if(parentNode === null){
return
}
const index = parentNode._childNodes.indexOf(this)
if(index < 0){
return
}
parentNode._removeObjectFromChildNodesAtIndex(index)
}
/**
* Removes a child from the node’s array of children and inserts another node in its place.
* @access public
* @param {SCNNode} oldChild -
* @param {SCNNode} newChild -
* @returns {void}
* @desc If both the child and child2 nodes are children of the node, calling this method swaps their positions in the array. Note that removing a node from the node hierarchy may result in it being deallocated.Calling this method results in undefined behavior if the child parameter does not refer to a child of this node.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408002-replacechildnode
*/
replaceChildNodeWith(oldChild, newChild) {
const index = this._childNodes.indexOf(oldChild)
if(index < 0){
return
}
this._removeObjectFromChildNodesAtIndex(index)
this._insertObjectInChildNodesAtIndex(newChild, index)
}
/**
*
* @access private
* @param {number} index -
* @returns {void}
*/
_removeObjectFromChildNodesAtIndex(index) {
const arr = this._childNodes.splice(index, 1)
if(arr.length === 0){
return
}
const obj = arr[0]
obj._parent = null
obj._transformUpToDate = false
}
/**
*
* @access private
* @param {SCNNode} object -
* @param {number} index -
* @returns {void}
*/
_insertObjectInChildNodesAtIndex(object, index) {
const length = this._childNodes.length
if(index > length){
throw new Error(`SCNNode.childNodes out of index: ${index} > ${length}`)
}
this._childNodes.splice(index, 0, object)
}
/**
* @access private
* @type {?SCNNode}
*/
get _rootNode() {
if(this._parent === null){
return this
}
return this._parent._rootNode
}
/**
* The node’s parent in the scene graph hierarchy.
* @type {?SCNNode}
* @desc For a scene’s rootNode object, the value of this property is nil.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407968-parent
*/
get parent() {
return this._parent
}
/**
* An array of the node’s children in the scene graph hierarchy.
* @type {SCNNode[]}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407984-childnodes
*/
get childNodes() {
return this._childNodes.slice(0)
}
// Searching the Node Hierarchy
/**
* Returns all nodes in the node’s child node subtree that satisfy the test applied by a block.
* @access public
* @param {function(child: SCNNode, stop: UnsafeMutablePointer<ObjCBool>): boolean} predicate - The block to apply to the node’s child and descendant nodes .The block takes two parameters:child The child node currently being searched. stop A reference to a Boolean value. Set *stop to true in the block to abort further processing of the child node subtree.The block returns a Boolean value indicating whether to include the child node in the search results array.
* @returns {SCNNode[]} -
* @desc Use this method to search for nodes using a test you specify. For example, you can search for empty nodes using a block that returns YES for nodes whose light, camera, and geometry properties are all nil.SceneKit uses a recursive preorder traversal to search the child node subtree—that is, the block searches a node before it searches each of the node’s children, and it searches all children of a node before searching any of that node’s sibling nodes.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407982-childnodes
*/
childNodesPassingTest(predicate) {
let result = []
return result
}
/**
* Returns the first node in the node’s child node subtree with the specified name.
* @access public
* @param {string} name - The name of the node to search for.
* @param {boolean} [recursively = true] - true to search the entire child node subtree, or false to search only the node’s immediate children.
* @returns {?SCNNode} -
* @desc If the recursive parameter is true, SceneKit uses a preorder traversal to search the child node subtree—that is, the block searches a node before it searches each of the node’s children, and it searches all children of a node before searching any of that node’s sibling nodes. Otherwise, SceneKit searches only those nodes in the node’s childNodes array.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407951-childnode
*/
childNodeWithNameRecursively(name, recursively = true) {
for(let i=0; i<this._childNodes.length; i++){
if(this._childNodes[i].name === name){
return this._childNodes[i]
}
if(recursively){
const result = this._childNodes[i].childNodeWithNameRecursively(name, recursively)
if(result !== null){
return result
}
}
}
return null
}
/**
* Returns the first node in the node’s child nodearray with the specified name.
* @access public
* @param {string} name - The name of the node to search for.
* @returns {?SCNNode} -
* @desc SceneKit searches only those nodes in the node’s childNodes array.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407951-childnode
*/
childNodeWithName(name) {
return this.childNodeWithNameRecursively(name, false)
}
/**
* @access private
* @param {string} nodeID -
* @param {boolean} recursively -
* @returns {?SCNNode} -
*/
_childNodeWithNodeIDRecursively(nodeID, recursively = true) {
for(let i=0; i<this._childNodes.length; i++){
if(this._childNodes[i]._nodeID === nodeID){
return this._childNodes[i]
}
if(recursively){
const result = this._childNodes[i]._childNodeWithNodeIDRecursively(nodeID, recursively)
if(result !== null){
return result
}
}
}
return null
}
/**
* @access private
* @param {string} nodeID -
* @returns {?SCNNode} -
*/
_childNodeWithNodeID(nodeID) {
return this._childNodeWithNodeIDRecursively(name, false)
}
/**
* Executes the specified block for each of the node’s child and descendant nodes.
* @access public
* @param {function(child: SCNNode, stop: UnsafeMutablePointer<ObjCBool>): void} block - The block to apply to the node’s child and descendant nodes.The block takes two parameters:childThe child node currently being evaluated.stopA reference to a Boolean value. Set *stop to true in the block to abort further processing of the child node subtree.
* @returns {void}
* @desc SceneKit uses a recursive preorder traversal to process the child node subtree—that is, the block runs for a node before it runs for each of the node’s children, and it processes all children of a node before processing any of that node’s sibling nodes.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408032-enumeratechildnodes
*/
enumerateChildNodes(block) {
//this._childNodes.some((child) => {
this.childNodes.some((child) => {
return this._enumerateChildNodesRecursive(child, block)
})
}
/**
* Executes the specified block for each of the node’s child and descendant nodes, as well as for the node itself.
* @access public
* @param {function(arg1: SCNNode, arg2: UnsafeMutablePointer<ObjCBool>): void} block - The block to apply to the node’s child and descendant nodes.The block takes two parameters:childThe child node currently being evaluated.stopA reference to a Boolean value. Set *stop to true in the block to abort further processing of the child node subtree.
* @returns {void}
* @desc SceneKit uses a recursive preorder traversal to process the child node subtree—that is, the block runs for a node before it runs for each of the node’s children, and it processes all children of a node before processing any of that node’s sibling nodes.This method is equivalent to the enumerateChildNodes(_:) method, but unlike that method it also runs the block to process the node itself, not just its child nodes.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1642248-enumeratehierarchy
*/
enumerateHierarchy(block) {
this._enumerateChildNodesRecursive(this, block)
}
_enumerateChildNodesRecursive(node, block) {
let stop = block(node)
if(stop === true){
return true
}
stop = node._childNodes.some((child) => {
return this._enumerateChildNodesRecursive(child, block)
})
return stop
}
// Adding Physics to a Node
get physicsBody() {
return this._physicsBody
}
set physicsBody(newValue) {
if(this._physicsBody){
this._physicsBody._node = null
}
this._physicsBody = newValue
if(this._physicsBody){
this._physicsBody._node = this
this._physicsBody.resetTransform()
}
}
_resetPhysicsTransformRecursively(updateWorldTransform = false) {
if(this._physicsBody){
this._physicsBody._resetTransform(updateWorldTransform)
}
for(const child of this._childNodes){
child._resetPhysicsTransformRecursively(updateWorldTransform)
}
}
// Working With Particle Systems
/**
* Attaches a particle system to the node.
* @access public
* @param {SCNParticleSystem} system - A particle system.
* @returns {void}
* @desc When attached to a node, a particle system’s emitter location follows that node as it moves through the scene. To instead attach a particle system to a location in the scene’s world coordinate space, use the corresponding method on SCNScene.For details on particle systems, see SCNParticleSystem.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1523123-addparticlesystem
*/
addParticleSystem(system) {
if(this._particleSystems === null){
this._particleSystems = []
}
system.reset()
this._particleSystems.push(system)
}
/**
* Removes a particle system attached to the node.
* @access public
* @param {SCNParticleSystem} system - A particle system.
* @returns {void}
* @desc This method has no effect if the system parameter does not reference a particle system directly attached to the node.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1524014-removeparticlesystem
*/
removeParticleSystem(system) {
if(this._particleSystems === null){
return
}
const index = this._particleSystems.indexOf(system)
this._particleSystems.splice(index, 1)
}
/**
* Removes any particle systems directly attached to the node.
* @access public
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1522801-removeallparticlesystems
*/
removeAllParticleSystems() {
this._particleSystems = []
}
/**
* The particle systems attached to the node.
* @access public
* @type {?SCNParticleSystem[]}
* @desc An array of SCNParticleSystem objects directly attached to the node. This array does not include particle systems attached to the node's child nodes. For details on particle systems, see SCNParticleSystem.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1522705-particlesystems
*/
get particleSystems() {
return this._particleSystems
}
// Working With Positional Audio
/**
* Adds the specified auto player to the node and begins playback.
* @access public
* @param {SCNAudioPlayer} player - An audio player object.
* @returns {void}
* @desc Positional audio effects from a player attached to a node are based on that node’s position relative to the audioListener position in the scene.After playback has completed, SceneKit automatically removes the audio player from the node.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1523464-addaudioplayer
*/
addAudioPlayer(player) {
if(this._audioPlayers.indexOf(player) < 0){
this._audioPlayers.push(player)
player._play()
}
}
/**
* Removes the specified audio player from the node, stopping playback.
* @access public
* @param {SCNAudioPlayer} player - An audio player attached to the node.
* @returns {void}
* @desc This method has no effect if the player parameter does not reference an audio player directly attached to the node.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1522767-removeaudioplayer
*/
removeAudioPlayer(player) {
const index = this._audioPlayers.indexOf(player)
if(index >= 0){
player._stop()
delete this._audioPlayers[index]
}
}
/**
* Removes all audio players attached to the node, stopping playback.
* @access public
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1523570-removeallaudioplayers
*/
removeAllAudioPlayers() {
this._audioPlayers.forEach((player) => {
player._stop()
})
this._audioPlayers = []
}
/**
* The audio players currently attached to the node.
* @type {SCNAudioPlayer[]}
* @desc Positional audio effects from a player attached to a node are based on that node’s position relative to the audioListener position in the scene.After an audio player completes playback, SceneKit automatically removes it from the node. Therefore, this array always contains audio players that are currently playing back audio.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1523244-audioplayers
*/
get audioPlayers() {
return this._audioPlayers.slice(0)
}
// Copying a Node
/**
* Creates a copy of the node and its children.
* @access public
* @returns {SCNNode} -
* @desc This method recursively copies the node and its child nodes. For a nonrecursive copy, use the inherited copy() method, which creates a copy of the node without any child nodes.Cloning or copying a node creates a duplicate of the node object, but not the geometries, lights, cameras, and other SceneKit objects attached to it—instead, each copied node shares references to these objects.This behavior means that you can use cloning to, for example, place the same geometry at several locations within a scene without maintaining multiple copies of the geometry and its materials. However, it also means that changes to the objects attached to one node will affect other nodes that share the same attachments. For example, to render two copies of a node using different materials, you must copy both the node and its geometry before assigning a new material.- (void)duplicateNode:(SCNNode *)node withMaterial:(SCNMaterial *)material
{
SCNNode *newNode = [node clone];
newNode.geometry = [node.geometry copy];
newNode.geometry.firstMaterial = material;
}
Multiple copies of an SCNGeometry object efficiently share the same vertex data, so you can copy geometries without a significant performance penalty.- (void)duplicateNode:(SCNNode *)node withMaterial:(SCNMaterial *)material
{
SCNNode *newNode = [node clone];
newNode.geometry = [node.geometry copy];
newNode.geometry.firstMaterial = material;
}
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408046-clone
*/
clone() {
const node = this.copy()
this._childNodes.forEach((child) => {
node.addChildNode(child.clone())
})
return node
}
/**
* Creates an optimized copy of the node and its children.
* @access public
* @returns {SCNNode} -
* @desc Rendering complex node hierarchies can incur a performance cost. Each geometry and material requires a separate draw command to be sent to the GPU, and each draw command comes with a performance overhead. If you plan for a portion of your scene’s node hierarchy to remain static (with respect to itself, if not the rest of the scene), use this method to create a single node containing all elements of that node hierarchy that SceneKit can render using fewer draw commands.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407960-flattenedclone
*/
flattenedClone() {
return null
}
// Hit-Testing
/**
* Searches the node’s child node subtree for objects intersecting a line segment between two specified points.
* @access public
* @param {SCNVector3} pointA - An endpoint of the line segment to search along, specified in the node’s local coordinate system.
* @param {SCNVector3} pointB - The other endpoint of the line segment to search along, specified in the node’s local coordinate system.
* @param {?Map<string, Object>} [options = null] - A dictionary of options affecting the search. See Hit Testing Options Keys for acceptable values.
* @returns {SCNHitTestResult[]} -
* @desc Hit-testing is the process of finding elements of a scene located along a specified line segment in the scene’s coordinate space (or that of a particular node in the scene). For example, you can use this method to determine whether a projectile launched by a game character will hit its target.To search for the scene element corresponding to a two-dimensional point in the rendered image, use the renderer’s hitTest(_:options:) method instead.
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407998-hittestwithsegment
*/
hitTestWithSegmentFromTo(pointA, pointB, options = null) {
const worldPointA = this.convertPositionTo(pointA, null)
const worldPointB = this.convertPositionTo(pointB, null)
const results = []
this.enumerateChildNodes((child) => {
if(child.presentation.geometry){
const hits = SCNPhysicsWorld._hitTestWithSegmentNode(worldPointA, worldPointB, child.presentation)
if(hits.length > 0){
// convert from the child's coordinate to this node's coordinate
for(const h of hits){
h._node = child
h._worldCoordinates = child.convertPositionTo(h._localCoordinates, null)
h._worldNormal = child.convertPositionTo(h._localNormal, null)
h._localCoordinates = this.convertPositionFrom(h._localCoordinates, child)
h._localNormal = this.convertPositionFrom(h._localNormal, child)
}
results.push(...hits)
}
}
})
// TODO: sort by the distance
if(results.length > 0){
console.error('hitTestWithSegmentFromTo: ' + results.length)
}
return results
}
// Converting Between Node Coordinate Spaces
/**
* Converts a position to the node’s coordinate space from that defined by another node.
* @access public
* @param {SCNVector3} position - A position in the local coordinate space defined by the other node.
* @param {?SCNNode} node - Another node in the same scene graph as the node, or nil to convert from the scene’s world coordinate space.
* @returns {SCNVector3} -
* @see https://developer.apple.com/documentation/scenekit/scnnode/1408018-convertposition
*/
convertPositionFrom(position, node) {
if(node === null){
return position.transform(this._worldTransform.invert())
}
return position.transform(node._worldTransform).transform(this._worldTransform.invert())
}
/**
* Converts a position from the node’s coordinate space to that defined by another node.
* @access public
* @param {SCNVector3} position - A position in the node’s local coordinate space.
* @param {?SCNNode} node - Another node in the same scene graph as the node, or nil to convert to the scene’s world coordinate space.
* @returns {SCNVector3} -
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407990-convertposition
*/
convertPositionTo(position, node) {
if(node === null){
return position.transform(this._worldTransform)
}
return position.transform(this._worldTransform).transform(node._worldTransform.invert())
}
/**
* Converts a transformation to the node’s coordinate space from that defined by another node.
* @access public
* @param {SCNMatrix4} transform - A transformation relative to the local coordinate space defined by the other node.
* @param {?SCNNode} node - Another node in the same scene graph as the node, or nil to convert from the scene’s world coordinate space.
* @returns {SCNMatrix4} -
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407996-converttransform
*/
convertTransformFrom(transform, node = null) {
if(node === null){
return transform.mult(this._worldTransform.invert())
}
return transform.mult(node._worldTransform).mult(this._worldTransform.invert())
}
/**
* Converts a transformation from the node’s coordinate space to that defined by another node.
* @access public
* @param {SCNMatrix4} transform - A transformation relative to the node’s coordinate space.
* @param {?SCNNode} node - Another node in the same scene graph as the node, or nil to convert to the scene’s world coordinate space.
* @returns {SCNMatrix4} -
* @see https://developer.apple.com/documentation/scenekit/scnnode/1407986-converttransform
*/
convertTransformTo(transform, node = null) {
if(node === null){
return transform.mult(this._worldTransform)
}
return transform.mult(this._worldTransform).mult(node._worldTransform.invert())
}
/**
*
* @type {SCNVector3}
* @desc
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867392-worldfront
*/
get worldFront() {
return _localFront.rotate(this.worldTransform)
}
/**
*
* @type {SCNVector3}
* @desc
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867404-worldright
*/
get worldRight() {
return _localRight.rotate(this.worldTransform)
}
/**
*
* @type {SCNVector3}
* @desc
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867395-worldup
*/
get worldUp() {
return _localUp.rotate(this.worldTransform)
}
/**
*
* @type {SCNVector3}
* @desc
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867393-localfront
*/
static get localFront() {
return _localFront
}
/**
*
* @type {SCNVector3}
* @desc
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867400-localright
*/
static get localRight() {
return _localRight
}
/**
*
* @type {SCNVector3}
* @desc
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867386-localup
*/
static get localUp() {
return _localUp
}
/**
*
* @access public
* @param {SCNVector3} vector -
* @param {?SCNNode} node -
* @returns {SCNVector3} -
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867403-convertvector
*/
convertVectorFrom(vector, node) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNVector3} vector -
* @param {?SCNNode} node -
* @returns {SCNVector3} -
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867397-convertvector
*/
convertVectorTo(vector, node) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNQuaternion} rotation -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867398-localrotate
*/
localRotateBy(rotation) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNVector3} translation -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867383-localtranslate
*/
localTranslateBy(translation) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNVector3} worldTarget -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867394-look
*/
lookAt(worldTarget) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNVector3} worldTarget -
* @param {SCNVector3} worldUp -
* @param {SCNVector3} localFront -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867396-look
*/
lookAtUp(worldTarget, worldUp, localFront) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNQuaternion} worldRotation -
* @param {SCNVector3} worldTarget -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867399-rotate
*/
rotateByAroundTarget(worldRotation, worldTarget) {
// TODO: implement
}
/**
*
* @access public
* @param {SCNMatrix4} worldTransform -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnnode/2867401-setworldtransform
*/
setWorldTransform(worldTransform) {
// TODO: implement
}
///////////////////
// SCNActionable //
///////////////////
// Running Actions
/**
* Required. Adds an action to the list of actions executed by the node.
* @access public
* @param {SCNAction} action - The action to be performed.
* @returns {void}
* @desc SceneKit begins running a newly added action when it prepares to render the next frame.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1523164-runaction
*/
runAction(action) {
this.runActionForKey(action, Symbol())
}
/**
* Required. Adds an action to the list of actions executed by the node. SceneKit calls the specified block when the action completes.
* @access public
* @param {SCNAction} action - The action to be performed.
* @param {?function(): void} [block = null] - A completion block that SceneKit calls when the action completes.
* @returns {void}
* @desc The new action is processed the next time SceneKit prepares to render a frame.SceneKit calls your block after the action’s duration is complete. For example, in a game you could use this method to show a Game Over message after performing a fade-out action on a node that displays a player character.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1524219-runaction
*/
runActionCompletionHandler(action, block = null) {
this.runActionForKeyCompletionHandler(action, Symbol(), block)
}
/**
* Required. Adds an identifiable action to the list of actions executed by the node.
* @access public
* @param {SCNAction} action - The action to be performed.
* @param {?string} key - A unique key used to identify the action.
* @returns {void}
* @desc This method is identical to runAction(_:), but the action is stored and identified so that you can retrieve or cancel it later. If an action using the same key is already running, SceneKit removes it before adding the new action.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1524222-runaction
*/
runActionForKey(action, key) {
this.runActionForKeyCompletionHandler(action, key, null)
}
/**
* Required. Adds an identifiable action to the list of actions executed by the node. SceneKit calls the specified block when the action completes.
* @access public
* @param {SCNAction} action - The action to be performed.
* @param {?string} key - A unique key used to identify the action.
* @param {?function(): void} [block = null] - A completion block called when the action completes.
* @returns {void}
* @desc This method is identical to runAction(_:completionHandler:), but the action is stored and identified so that you can retrieve or cancel it later. If an action using the same key is already running, SceneKit removes it before adding the new action.SceneKit calls your block after the action’s duration is complete. For example, you can use this method with a wait action to execute some code after a timed delay. If during the delay period you need to prevent the code from running, use the removeAction(forKey:) method to cancel it.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1522791-runaction
*/
runActionForKeyCompletionHandler(action, key, block = null) {
if(typeof key === 'undefined' || key === null){
key = Symbol()
}
const act = action.copy()
// FIXME: use current frame time
act._actionStartTime = Date.now() * 0.001
act._completionHandler = block
this._actions.set(key, act)
//this._copyTransformToPresentationRecursive()
}
// Inspecting a Node’s Running Actions
/**
* Required. Returns an action associated with a specific key.
* @access public
* @param {string} key - A string that uniquely identifies a action.
* @returns {?SCNAction} -
* @desc Use this method to retrieve actions you scheduled using the runAction(_:forKey:) or runAction(_:forKey:completionHandler:) method.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1523287-action
*/
actionForKey(key) {
return this._actions.get(key)
}
/**
* Required. A Boolean value that indicates whether the node is currently executing any actions.
* @type {boolean}
* @desc This value is true if the node has any executing actions; otherwise the value is false.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1523794-hasactions
*/
get hasActions() {
return this._actions.size > 0
}
/**
* Required. The list of keys for which the node has attached actions.
* @type {string[]}
* @desc Use this property to list actions you scheduled using the runAction(_:forKey:) or runAction(_:forKey:completionHandler:) method.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1523036-actionkeys
*/
get actionKeys() {
const keys = []
for(const key of this._actions.keys()){
keys.push(key)
}
return keys
}
// Canceling a Node’s Running Actions
/**
* Required. Removes an action associated with a specific key.
* @access public
* @param {string} key - A string that uniquely identifies a action.
* @returns {void}
* @desc If the node is currently running an action that matches the key, SceneKit removes that action from the node, skipping any remaining animation it would perform but keeping any changes already made to the node.Use this method to cancel actions you scheduled using the runAction(_:forKey:) or runAction(_:forKey:completionHandler:) method.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1523617-removeaction
*/
removeActionForKey(key) {
// TODO: stop action
this._actions.delete(key)
}
/**
* Required. Ends and removes all actions from the node.
* @access public
* @returns {void}
* @desc When SceneKit removes an action from a node, it skips any remaining animation the action would perform. However, any changes the action has already made to the node’s state remain in effect.
* @see https://developer.apple.com/documentation/scenekit/scnactionable/1524181-removeallactions
*/
removeAllActions() {
// TODO: stop actions
this._actions.clear()
}
///////////////////
// SCNAnimatable //
///////////////////
// Managing Animations
/**
* Required. Adds an animation object for the specified key.
* @access public
* @param {CAAnimation} animation - The animation object to be added.
* @param {?string} key - An string identifying the animation for later retrieval. You may pass nil if you don’t need to reference the animation later.
* @returns {void}
* @desc Newly added animations begin executing after the current run loop cycle ends.SceneKit does not define any requirements for the contents of the key parameter—it need only be unique among the keys for other animations you add. If you add an animation with an existing key, this method overwrites the existing animation.
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523386-addanimation
*/
addAnimationForKey(animation, key) {
if(typeof key === 'undefined' || key === null){
key = Symbol()
}
//const anim = animation.copy()
const anim = animation
// FIXME: use current frame time
anim._animationStartTime = Date.now() * 0.001
this._animations.set(key, anim)
this._copyTransformToPresentationRecursive()
}
/**
* @access private
* @param {CAAnimation} animatino -
* @param {number} time -
* @returns {void}
*/
/*
_setAnimationStartTime(animation, time) {
animation._animationStartTime = time
animation._prevTime = time - 0.0000001
if(animation instanceof CAAnimationGroup){
animation.animations.forEach((anim) => {
this._setAnimationStartTime(anim, time)
})
}
}
*/
/**
* Required. Returns the animation with the specified key.
* @access public
* @param {string} key - A string identifying a previously added animation.
* @returns {?CAAnimation} -
* @desc Attempting to modify any properties of the returned object results in undefined behavior.
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1524020-animation
*/
animationForKey(key) {
return this._animations.get(key)
}
/**
* Required. Removes all the animations currently attached to the object.
* @access public
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1522762-removeallanimations
*/
removeAllAnimations() {
// TODO: stop animations
this._animations.clear()
}
/**
* Required. Removes the animation attached to the object with the specified key.
* @access public
* @param {string} key - A string identifying an attached animation to remove.
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1522880-removeanimation
*/
removeAnimationForKey(key) {
this.removeAnimationForKeyBlendOutDuration(key, 0)
}
/**
*
* @access public
* @param {string} key -
* @param {number} duration -
* @returns {void}
*/
removeAnimationForKeyBlendOutDuration(key, duration) {
// FIXME: use duration
this._animations.delete(key)
this._copyTransformToPresentationRecursive()
}
/**
* Required. Removes the animation attached to the object with the specified key, smoothly transitioning out of the animation’s effect.
* @access public
* @param {string} key - A string identifying an attached animation to remove.
* @param {number} duration - The duration for transitioning out of the animation’s effect before it is removed.
* @returns {void}
* @desc Use this method to create smooth transitions between the effects of multiple animations. For example, the geometry loaded from a scene file for a game character may have associated animations for player actions such as walking and jumping. When the player lands from a jump, you remove the jump animation so the character continues walking. If you use the removeAnimation(forKey:) method to remove the jump animation, SceneKit abruptly switches from the current frame of the jump animation to the current frame of the walk animation. If you use the removeAnimation(forKey:fadeOutDuration:) method instead, SceneKit plays both animations at once during that duration and interpolates vertex positions from one animation to the other, creating a smooth transition.
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1522841-removeanimation
*/
removeAnimationForKeyFadeOutDuration(key, duration) {
// FIXME: use fadeout duration
this.removeAnimationForKey(key)
}
/**
* Required. An array containing the keys of all animations currently attached to the object.
* @type {string[]}
* @desc This array contains all keys for which animations are attached to the object, or is empty if there are no attached animations. The ordering of animation keys in the array is arbitrary.
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523610-animationkeys
*/
get animationKeys() {
const keys = []
for(const key of this._animations.keys()){
keys.push(key)
}
return keys
}
// Pausing and Resuming Animations
/**
* Required. Pauses the animation attached to the object with the specified key.
* @access public
* @param {string} key - A string identifying an attached animation.
* @returns {void}
* @desc This method has no effect if no animation is attached to the object with the specified key.
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523592-pauseanimation
*/
pauseAnimationForKey(key) {
}
/**
* Required. Resumes a previously paused animation attached to the object with the specified key.
* @access public
* @param {string} key - A string identifying an attached animation.
* @returns {void}
* @desc This method has no effect if no animation is attached to the object with the specified key or if the specified animation is not currently paused.
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523332-resumeanimation
*/
resumeAnimationForKey(key) {
}
/**
* Required. Returns a Boolean value indicating whether the animation attached to the object with the specified key is paused.
* @access public
* @param {string} key - A string identifying an attached animation.
* @returns {boolean} -
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523703-isanimationpaused
*/
isAnimationPausedForKey(key) {
return false
}
// Instance Methods
/**
* Required.
* @access public
* @param {number} speed -
* @param {string} key -
* @returns {void}
* @see https://developer.apple.com/documentation/scenekit/scnanimatable/1778343-setanimationspeed
*/
setAnimationSpeedForKey(speed, key) {
}
_setAnimationsToPlayers() {
const len = this._animationPlayers._values.length
if(len > 0 && this._animations._values.length > 0){
for(let i=0; i<len; i++){
this._animationPlayers._values[i]._animation = this._animations._values[i]
}
}
}
/**
*
* @access public
* @param {SCNAnimationPlayer} player -
* @param {?string} key -
* @returns {void}
*/
addAnimationPlayerForKey(player, key) {
if(typeof key === 'undefined' || key === null){
key = Symbol()
}
this._animationPlayers.set(key, player)
this._copyTransformToPresentationRecursive()
}
/**
*
* @access public
* @param {string} key -
* @returns {SCNAnimationPlayer} -
*/
animationPlayerForKey(key) {
return this._animationPlayers.get(key)
}
///////////////////////
// SCNBoundingVolume //
///////////////////////
// Working with Bounding Volumes
/**
* The minimum and maximum corner points of the object’s bounding box.
* @type {{min: SCNVector3, max: SCNVector3}}
* @see https://developer.apple.com/documentation/scenekit/scnboundingvolume/2034705-boundingbox
*/
get boundingBox() {
if(this._fixedBoundingBox){
return this._fixedBoundingBox
}
this._updateBoundingBox()
return this._boundingBox
}
set boundingBox(newValue) {
this._fixedBoundingBox = newValue
}
/**
* The center point and radius of the object’s bounding sphere.
* @type {{center: SCNVector3, radius: number}}
* @desc Scene Kit defines a bounding sphere in the local coordinate space using a center point and a radius. For example, if a node’s bounding sphere has the center point {3, 1, 4} and radius 2.0, all points in the vertex data of node’s geometry (and any geometry attached to its child nodes) lie within 2.0 units of the center point.The coordinates provided when reading this property are valid only if the object has a volume to be measured. For a geometry containing no vertex data or a node containing no geometry (and whose child nodes, if any, contain no geometry), the values center and radius are both zero.
* @see https://developer.apple.com/documentation/scenekit/scnboundingvolume/2034707-boundingsphere
*/
get boundingSphere() {
// TODO: calculate bounding sphere
return {center: new SCNVector3(), radius: 0}
}
_geometryBoundingBox() {
if(this._geometry === null){
return {
min: new SCNVector3(Infinity, Infinity, Infinity),
max: new SCNVector3(-Infinity, -Infinity, -Infinity)
}
}
const boundingBox = this._geometry.boundingBox
// FIXME: rotate and scale
if(this.skinner && this.skinner.baseGeometryBindTransform){
const tx = this.skinner.baseGeometryBindTransform.m41
const ty = this.skinner.baseGeometryBindTransform.m42
const tz = this.skinner.baseGeometryBindTransform.m43
boundingBox.min.x += tx
boundingBox.min.y += ty
boundingBox.min.z += tz
boundingBox.max.x += tx
boundingBox.max.y += ty
boundingBox.max.z += tz
}
//return this._geometry.boundingBox
//return this._geometry._updateBoundingBoxForSkinner(this.skinner)
return boundingBox
}
_updateBoundingBox() {
// FIXME: use rotation of the node
let box = this._geometryBoundingBox()
const p = this._presentation ? this._presentation : this
if(p.geometry !== null){
if(box === null){
box = p.geometry._updateBoundingBox()
}
box = this._unionBoundingBox(box, p.geometry.boundingBox)
}
const scale = p._scale
if(scale.x < 0){
const minX = box.max.x * scale.x
const maxX = box.min.x * scale.x
box.min.x = minX
box.max.x = maxX
}else{
box.min.x *= scale.x
box.max.x *= scale.x
}
if(scale.y < 0){
const minY = box.max.y * scale.y
const maxY = box.min.y * scale.y
box.min.y = minY
box.max.y = maxY
}else{
box.min.y *= scale.y
box.max.y *= scale.y
}
if(scale.z < 0){
const minZ = box.max.z * scale.z
const maxZ = box.min.z * scale.z
box.min.z = minZ
box.max.z = maxZ
}else{
box.min.z *= scale.z
box.max.z *= scale.z
}
for(const child of this._childNodes){
const cbox = child._updateBoundingBox()
box = this._unionChildBoundingBox(box, cbox)
}
this._boundingBox = box
return box
}
_unionBoundingBox(box1, box2) {
if(box1 === null){
return box2
}
if(box2 === null){
return box1
}
const min = new SCNVector3()
const max = new SCNVector3()
min.x = Math.min(box1.min.x, box2.min.x)
min.y = Math.min(box1.min.y, box2.min.y)
min.z = Math.min(box1.min.z, box2.min.z)
max.x = Math.max(box1.max.x, box2.max.x)
max.y = Math.max(box1.max.y, box2.max.y)
max.z = Math.max(box1.max.z, box2.max.z)
return { min: min, max: max }
}
_unionChildBoundingBox(box, cbox) {
const p = this._presentation ? this._presentation : this
const pos = p._position
const scale = p._scale
const min = new SCNVector3(
(cbox.min.x + pos.x) * scale.x,
(cbox.min.y + pos.y) * scale.y,
(cbox.min.z + pos.z) * scale.z
)
const max = new SCNVector3(
(cbox.max.x + pos.x) * scale.x,
(cbox.max.y + pos.y) * scale.y,
(cbox.max.z + pos.z) * scale.z
)
return this._unionBoundingBox(box, { min: min, max: max })
}
_updateTransform() {
const m1 = SCNMatrix4.matrixWithScale(this._scale)
const m2 = m1.rotation(this._rotation)
const m3 = m2.translation(this._position)
this._transform = m3
this._transformUpToDate = true
}
/**
*
* @access public
* @returns {SCNNode} -
*/
copy() {
const node = new SCNNode()
node.name = this.name
node.light = this.light
node.camera = this.camera
node._geometry = this._geometry
node.morpher = this.morpher ? this.morpher._copy() : null
node.skinner = this.skinner
node.categoryBitMask = this.categoryBitMask
node.isPaused = this.isPaused
node._presentation = this._presentation ? this._presentation.copy() : null
node._isPresentationInstance = this._isPresentationInstance
node.constraints = this.constraints ? this.constraints.slice(0) : null
node.isHidden = this.isHidden
node._opacity = this._opacity
node.renderingOrder = this.renderingOrder
node.castsShadow = this.castsShadow
node.movabilityHint = this.movabilityHint
node.filters = this.filters ? this.filters.slice(0) : null
node.rendererDelegate = this.rendererDelegate
node._physicsBody = this._physicsBody // FIXME: copy
node.physicsField = this.physicsField
node._particleSystems = this._particleSystems ? this._particleSystems.slice(0) : null
node._audioPlayers = this._audioPlayers
//node._hasActions = this._hasActions
node._actions = new Map(this._actions)
node._animations = this._animations.copy()
node._boundingBox = this._boundingBox
//node._boundingSphere = this._boundingSphere
node._position = new SCNVector3(this._position.x, this._position.y, this._position.z)
node._rotation = new SCNVector4(this._rotation.x, this._rotation.y, this._rotation.z, this._rotation.w)
node._scale = new SCNVector3(this._scale.x, this._scale.y, this._scale.z)
node._transformUpToDate = false
return node
}
_copyTransformToPresentation() {
if(this._presentation === null){
return
}
const p = this._presentation
p._position = this._position._copy()
p._rotation = this._rotation._copy()
p._scale = this._scale._copy()
}
_copyTransformToPresentationRecursive() {
const nodes = [this]
while(nodes.length > 0){
const node = nodes.shift()
node._copyTransformToPresentation()
nodes.push(...node._childNodes)
}
}
_copyMaterialPropertiesToPresentation() {
const p = this._presentation
if(this._geometry){
for(const material of this._geometry.materials){
material._copyPresentationProperties()
}
}
p.opacity = this.opacity
}
_copyMorpherToPresentation() {
const p = this._presentation
if(this.morpher){
p.morpher.targets = this.morpher.targets.slice(0)
p.morpher._weights = this.morpher._weights.slice(0)
p.morpher.calculationMode = this.morpher.calculationMode
}
}
get viewTransform() {
return this.worldTransform.invert()
}
get inverseViewTransform() {
return this.worldTransform
}
get projectionTransform() {
if(this.camera === null){
return null
}
return this.camera.projectionTransform
}
get viewProjectionTransform() {
if(this.camera === null){
return null
}
const proj = this.camera.projectionTransform
const view = this.viewTransform
return view.mult(proj)
}
get lightViewProjectionTransform() {
if(this.light === null){
return null
}
this.light._updateProjectionTransform()
const proj = this.light._projectionTransform
const view = this.viewTransform
return view.mult(proj)
}
get shadowProjectionTransform() {
if(this.light === null){
return null
}
const vp = this.lightViewProjectionTransform
const scale = SCNMatrix4MakeTranslation(1.0, 1.0, 0.0).scale(0.5, 0.5, 1.0) // [-1, 1] => [0, 1]
return vp.mult(scale)
}
/**
* Invoked by value(forKey:) when it finds no property corresponding to a given key.
* @access public
* @param {string} key - A string that is not equal to the name of any of the receiver's properties.
* @returns {?Object} -
* @desc Subclasses can override this method to return an alternate value for undefined keys. The default implementation raises an NSUndefinedKeyException.
* @see https://developer.apple.com/documentation/objectivec/nsobject/1413457-value
*/
valueForUndefinedKey(key) {
if(key.charAt(0) === '/'){
const nodeID = key.substr(1)
if(this._nodeID === nodeID){
return this
}
let node = this._childNodeWithNodeIDRecursively(nodeID)
if(node){
return node
}
node = this.childNodeWithNameRecursively(nodeID)
if(node){
return node
}
const rootNode = this._rootNode
if(rootNode !== this){
node = rootNode._childNodeWithNodeIDRecursively(nodeID)
if(node){
return node
}
node = rootNode.childNodeWithNameRecursively(nodeID)
if(node){
return node
}
}
}
return super.valueForUndefinedKey(key)
}
valueForKeyPath(keyPath, usePresentation = true) {
const target = (usePresentation && this._presentation) ? this._presentation : this
const paths = keyPath.split('.')
const key = paths[0]
const key2 = paths[1]
if(key === 'position'){
if(key2){
return target.position[key2]
}
return target.position
}else if(key === 'rotation'){
if(key2){
return target.rotation[key2]
}
return target.rotation
}else if(key === 'scale'){
if(key2){
return target.scale[key2]
}
return target.scale
}else if(key === 'eulerAngles'){
if(key2){
return target.eulerAngles[key2]
}
return target.eulerAngles
}else if(key === 'orientation'){
if(key2){
return target.orientation[key2]
}
return target.orientation
}else if(key === 'transform'){
if(key2){
return target.transform[key2]
}
return target.transform
}
return super.valueForKeyPath(keyPath, usePresentation)
}
setValueForKey(value, key) {
// FIXME: check flags to decide to use a presentation node
const target = this._presentation ? this._presentation : this
if(key === 'position'){
target.position = value
}else if(key === 'rotation'){
target.rotation = value
}else if(key === 'scale'){
target.scale = value
}else if(key === 'eulerAngles'){
target.eulerAngles = value
}else if(key === 'orientation'){
target.orientation = value
}else if(key === 'transform'){
target.transform = value
}else{
super.setValueForKey(value, key)
}
}
setValueForKeyPath(value, keyPath) {
const target = this._presentation ? this._presentation : this
const paths = keyPath.split('.')
const key = paths.shift()
const restPath = paths.join('.')
//console.log(`SCNNode setValueForKeyPath ${this.name} ${key} ${restPath}`)
if(key === 'transform'){
switch(restPath){
case 'rotation.x':
target._rotation.x = value
target._transformUpToDate = false
return
case 'rotation.y':
target._rotation.y = value
target._transformUpToDate = false
return
case 'rotation.z':
target._rotation.z = value
target._transformUpToDate = false
return
case 'rotation':
target._rotation.z = value
target._transformUpToDate = false
return
case 'quaternion':
target.orientation = value
target._transformUpToDate = false
return
case 'scale.x':
target._scale.x = value
target._transformUpToDate = false
return
case 'scale.y':
target._scale.y = value
target._transformUpToDate = false
return
case 'scale.z':
target._scale.z = value
target._transformUpToDate = false
return
case 'scale': {
target._scale.x = value.x
target._scale.y = value.y
target._scale.z = value.z
target._transformUpToDate = false
return
}
case 'translation.x':
target._position.x = value
target._transformUpToDate = false
return
case 'translation.y':
target._position.y = value
target._transformUpToDate = false
return
case 'translation.z':
target._position.z = value
target._transformUpToDate = false
return
case 'translation':
target._position.x = value.x
target._position.y = value.y
target._transformUpToDate = false
return
default:
// do nothing
}
}else if(key === 'position'){
if(restPath !== ''){
target._position[restPath] = value
}else{
target._position = value
}
return
}else if(key === 'rotation'){
if(restPath !== ''){
target._rotation[restPath] = value
}else{
target._rotation = value
}
return
}else if(key === 'orientation'){
if(restPath !== ''){
const v = target.orientation
v[restPath] = value
target.orientation = v
}else{
target.orientation = value
}
return
}else if(key === 'eulerAngles'){
if(restPath !== ''){
const v = target.eulerAngles
v[restPath] = value
target.eulerAngles = v
}else{
target.eulerAngles = value
}
return
}else if(key === 'scale'){
if(restPath !== ''){
target._scale[restPath] = value
}else{
target._scale = value
}
return
}else if(key === 'morpher'){
if(target.morpher === null){
throw new Error('target morpher === null')
}
target.morpher.setValueForKeyPath(value, restPath)
return
}
// TODO: add other properties
super.setValueForKeyPath(value, keyPath)
}
/**
* @access private
* @returns {Ammo.btTransform} -
* @desc call Ammo.destroy(transform) after using it.
*/
_createBtTransform() {
//const transform = new Ammo.btTransform()
//const pos = this.position.createBtVector3()
//const rot = this.orientation.craeteBtQuaternion()
//transform.setIdentity()
//transform.setOrigin(pos)
//transform.setRotation(rot)
//Ammo.destroy(pos)
//Ammo.destroy(rot)
//return transform
}
_createBtCollisionShape() {
//if(this._geometry === null){
// throw new Error('geometry is null')
//}
//return this._geometry._createBtCollisionShape()
}
destory() {
//if(this.physicsBody !== null){
// this.physicsBody.destory()
// this.physicsBody = null
//}
//if(this._geometry !== null){
// // the geometry might be shared with other nodes...
// //this.geometry.destroy()
//}
}
/**
* @access private
* @returns {void}
*/
_resetPromise() {
this._loadedPromise = null
}
/**
* @access private
* @returns {void}
*/
_resetPromiseRecursively() {
this._resetPromise()
for(const child of this._childNodes){
child._resetPromiseRecursively()
}
}
/**
* @access private
* @returns {Promise} -
*/
_getLoadedPromise() {
if(this._loadedPromise){
return this._loadedPromise
}
const promises = []
for(const child of this._childNodes){
promises.push(child.didLoad)
}
if(this._particleSystems){
for(const system of this._particleSystems){
promises.push(system.didLoad)
}
}
if(this._geometry){
promises.push(this._geometry.didLoad)
}
for(const player of this._audioPlayers){
promises.push(player.didLoad)
}
//this._loadedPromise = Promise.all(promises)
//return this._loadedPromise
return Promise.all(promises)
}
/**
* @access public
* @returns {Promise} -
*/
get didLoad() {
return this._getLoadedPromise()
}
}