Home Reference Source Repository

js/SceneKit/SCNMaterialProperty.js

  1. 'use strict'
  2.  
  3. import NSObject from '../ObjectiveC/NSObject'
  4. //import SCNAnimatable from './SCNAnimatable'
  5. import SCNFilterMode from './SCNFilterMode'
  6. //import SCNMatrix4 from './SCNMatrix4'
  7. import SCNMatrix4MakeTranslation from './SCNMatrix4MakeTranslation'
  8. import SCNOrderedDictionary from './SCNOrderedDictionary'
  9. import SCNTransaction from './SCNTransaction'
  10. import SCNWrapMode from './SCNWrapMode'
  11. import SKColor from '../SpriteKit/SKColor'
  12. import _InstanceOf from '../util/_InstanceOf'
  13.  
  14.  
  15. /**
  16. * A container for the color or texture of one of a material’s visual properties.
  17. * @access public
  18. * @extends {NSObject}
  19. * @implements {SCNAnimatable}
  20. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty
  21. */
  22. export default class SCNMaterialProperty extends NSObject {
  23. static get _propTypes() {
  24. return {
  25. color: ['NSColor', '_contents'],
  26. image: ['NSMutableDictionary', (obj, dict, key, coder) => {
  27. let path = ''
  28. if(typeof dict.path !== 'undefined'){
  29. path = dict.path
  30. }else if(typeof dict.URL !== 'undefined'){
  31. path = dict.URL
  32. }
  33. obj._loadContentsImage(path, coder._directoryPath)
  34. }],
  35. float: ['float', (obj, value) => {
  36. obj._contents = new SKColor(value, value, value, 1.0)
  37. }],
  38. intensity: 'float',
  39. // contentsTransform
  40. wrapS: 'integer',
  41. wrapT: 'integer',
  42. minificationFilter: 'integer',
  43. magnificationFilter: 'integer',
  44. mipFilter: 'integer',
  45. maxAnisotropy: 'float',
  46. mappingChannel: 'integer',
  47. borderColor: 'plist',
  48.  
  49. propertyType: ['integer', null],
  50. parent: ['SCNMaterial', '_parent'],
  51. isCommonProfileProperty: ['boolean', null],
  52. sRGB: ['boolean', null],
  53. customSlotName: ['string', null]
  54. }
  55. }
  56.  
  57. // Creating a Material Property
  58.  
  59. /**
  60. * Creates a new material property object with the specified contents.
  61. * @access public
  62. * @constructor
  63. * @param {Object} contents - The visual contents of the material property—a color, image, or source of animated content. For details, see the discussion of the contents property.
  64. * @desc Newly created SCNMaterial objects contain SCNMaterialProperty instances for all of their visual properties. To change a material’s visual properties, you modify those instances rather than creating new material property objects.You create new SCNMaterialProperty instances to provide textures for use with custom GLSL shaders—for details, see SCNShadable.
  65. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395386-init
  66. */
  67. constructor(contents = null) {
  68. super()
  69.  
  70. // Working with Material Property Contents
  71.  
  72. /**
  73. * The visual contents of the material property—a color, image, or source of animated content. Animatable.
  74. * @access private
  75. * @type {?Object}
  76. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395372-contents
  77. */
  78. this._contents = contents
  79.  
  80. /**
  81. * A number between 0.0 and 1.0 that modulates the effect of the material property. Animatable.
  82. * @type {number}
  83. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395407-intensity
  84. */
  85. this.intensity = 0
  86.  
  87.  
  88. // Configuring Texture Mapping Attributes
  89.  
  90. /**
  91. * The transformation applied to the material property’s visual contents. Animatable.
  92. * @type {SCNMatrix4}
  93. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395388-contentstransform
  94. */
  95. this.contentsTransform = SCNMatrix4MakeTranslation(0, 0, 0)
  96.  
  97. /**
  98. * The wrapping behavior for the S texture coordinate.
  99. * @type {SCNWrapMode}
  100. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395384-wraps
  101. */
  102. this.wrapS = SCNWrapMode.clamp
  103.  
  104. /**
  105. * The wrapping behavior for the T texture coordinate.
  106. * @type {SCNWrapMode}
  107. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395382-wrapt
  108. */
  109. this.wrapT = SCNWrapMode.clamp
  110.  
  111. /**
  112. * Texture filtering for rendering the material property’s image contents at a size smaller than that of the original image.
  113. * @type {SCNFilterMode}
  114. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395390-minificationfilter
  115. */
  116. this.minificationFilter = SCNFilterMode.linear
  117.  
  118. /**
  119. * Texture filtering for rendering the material property’s image contents at a size larger than that of the original image.
  120. * @type {SCNFilterMode}
  121. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395378-magnificationfilter
  122. */
  123. this.magnificationFilter = SCNFilterMode.linear
  124.  
  125. /**
  126. * Texture filtering for using mipmaps to render the material property’s image contents at a size smaller than that of the original image.
  127. * @type {SCNFilterMode}
  128. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395398-mipfilter
  129. */
  130. this.mipFilter = SCNFilterMode.nearest
  131.  
  132. /**
  133. * The amount of anisotropic texture filtering to be used when rendering the material property’s image contents.
  134. * @type {number}
  135. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395402-maxanisotropy
  136. */
  137. this.maxAnisotropy = 0
  138.  
  139. /**
  140. * The source of texture coordinates for mapping the material property’s image contents.
  141. * @type {number}
  142. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395405-mappingchannel
  143. */
  144. this.mappingChannel = 0
  145.  
  146. /**
  147. * A color used to fill in areas of a material’s surface not covered by the material property’s image contents.
  148. * @type {?Object}
  149. * @deprecated
  150. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395376-bordercolor
  151. */
  152. this.borderColor = null
  153.  
  154. /**
  155. * @access private
  156. * @type {SCNMaterial}
  157. */
  158. this._parent = null
  159.  
  160. ///////////////////
  161. // SCNAnimatable //
  162. ///////////////////
  163.  
  164. /**
  165. * @access private
  166. * @type {Map}
  167. */
  168. this._animations = new SCNOrderedDictionary()
  169.  
  170. this.__presentation = null
  171.  
  172. /**
  173. * @access private
  174. * @type {Promise}
  175. */
  176. this._loadedPromise = null
  177. }
  178.  
  179. _createPresentation() {
  180. if(this.__presentation === null){
  181. this.__presentation = this.copy()
  182. }
  183. }
  184.  
  185. _copyPresentation() {
  186. // TODO: copy other properties
  187. this.__presentation._contents = this._contents
  188. }
  189.  
  190. get _presentation() {
  191. if(this.__presentation === null){
  192. return null
  193. }
  194. return this.__presentation
  195. }
  196.  
  197. /**
  198. *
  199. * @access public
  200. * @returns {SCNMaterialProperty} -
  201. */
  202. copy() {
  203. const p = new SCNMaterialProperty()
  204. p._contents = this._contents // TODO: copy
  205. p.intensity = this.intensity
  206. p.contentsTransform = this.contentsTransform // TODO: copy
  207. p.wrapS = this.wrapS
  208. p.wrapT = this.wrapT
  209. p.minificationFilter = this.minificationFilter
  210. p.magnicifactionFilter = this.maginicifactionFilter
  211. p.mipFilter = this.mipFilter
  212. p.maxAnisotropy = this.maxAnisotropy
  213. p.mappingChannel = this.mappingChannel
  214. p.borderColor = this.borderColor // TODO: copy
  215. //p._parent
  216. //p._animations
  217. //p._presentation
  218.  
  219. return p
  220. }
  221.  
  222. valueForKeyPath(keyPath) {
  223. const target = this.__presentation ? this.__presentation : this
  224.  
  225. // TODO: add other keys
  226. if(keyPath === 'contents'){
  227. return target._contents
  228. }
  229.  
  230. return super.valueForKeyPath(keyPath)
  231. }
  232.  
  233. setValueForKeyPath(value, keyPath) {
  234. const target = this.__presentation ? this.__presentation : this
  235.  
  236. // TODO: add other keys
  237. if(keyPath === 'contents'){
  238. target._contents = value
  239. }else{
  240. super.setValueForKeyPath(value, keyPath)
  241. }
  242. }
  243.  
  244. /**
  245. * The visual contents of the material property—a color, image, or source of animated content. Animatable.
  246. * @type {?Object}
  247. * @see https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395372-contents
  248. */
  249. get contents() {
  250. return this._contents
  251. }
  252.  
  253. set contents(newValue) {
  254. const oldValue = this._contents
  255. this._contents = newValue
  256. SCNTransaction._addChange(this, 'contents', oldValue, newValue)
  257. }
  258.  
  259. ///////////////////
  260. // SCNAnimatable //
  261. ///////////////////
  262.  
  263. // Managing Animations
  264.  
  265. /**
  266. * Required. Adds an animation object for the specified key.
  267. * @access public
  268. * @param {CAAnimation} animation - The animation object to be added.
  269. * @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.
  270. * @returns {void}
  271. * @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.
  272. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523386-addanimation
  273. */
  274. addAnimationForKey(animation, key) {
  275. //console.log('SCNMaterialProperty addAnimationForKey')
  276. if(typeof key === 'undefined' || key === null){
  277. key = Symbol()
  278. }
  279. const anim = animation.copy()
  280. // FIXME: use current frame time
  281. anim._animationStartTime = Date.now() * 0.001
  282. anim._prevTime = anim._animationStartTime - 0.0000001
  283.  
  284. this._animations.set(key, anim)
  285. }
  286.  
  287. /**
  288. * Required. Returns the animation with the specified key.
  289. * @access public
  290. * @param {string} key - A string identifying a previously added animation.
  291. * @returns {?CAAnimation} -
  292. * @desc Attempting to modify any properties of the returned object results in undefined behavior.
  293. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1524020-animation
  294. */
  295. animationForKey(key) {
  296. return this._animations.get(key)
  297. }
  298.  
  299. /**
  300. * Required. Removes all the animations currently attached to the object.
  301. * @access public
  302. * @returns {void}
  303. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1522762-removeallanimations
  304. */
  305. removeAllAnimations() {
  306. this._animations.clear()
  307. }
  308.  
  309. /**
  310. * Required. Removes the animation attached to the object with the specified key.
  311. * @access public
  312. * @param {string} key - A string identifying an attached animation to remove.
  313. * @returns {void}
  314. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1522880-removeanimation
  315. */
  316. removeAnimationForKey(key) {
  317. this._animations.delete(key)
  318. // TODO: reset values
  319. }
  320.  
  321. /**
  322. * Required. Removes the animation attached to the object with the specified key, smoothly transitioning out of the animation’s effect.
  323. * @access public
  324. * @param {string} key - A string identifying an attached animation to remove.
  325. * @param {number} duration - The duration for transitioning out of the animation’s effect before it is removed.
  326. * @returns {void}
  327. * @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.
  328. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1522841-removeanimation
  329. */
  330. removeAnimationForKeyFadeOutDuration(key, duration) {
  331. }
  332.  
  333. /**
  334. * Required. An array containing the keys of all animations currently attached to the object.
  335. * @type {string[]}
  336. * @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.
  337. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523610-animationkeys
  338. */
  339. get animationKeys() {
  340. const keys = []
  341. for(const key of this._animations.keys()){
  342. keys.push(key)
  343. }
  344. return keys
  345. }
  346.  
  347. // Pausing and Resuming Animations
  348.  
  349. /**
  350. * Required. Pauses the animation attached to the object with the specified key.
  351. * @access public
  352. * @param {string} key - A string identifying an attached animation.
  353. * @returns {void}
  354. * @desc This method has no effect if no animation is attached to the object with the specified key.
  355. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523592-pauseanimation
  356. */
  357. pauseAnimationForKey(key) {
  358. }
  359.  
  360. /**
  361. * Required. Resumes a previously paused animation attached to the object with the specified key.
  362. * @access public
  363. * @param {string} key - A string identifying an attached animation.
  364. * @returns {void}
  365. * @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.
  366. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523332-resumeanimation
  367. */
  368. resumeAnimationForKey(key) {
  369. }
  370.  
  371. /**
  372. * Required. Returns a Boolean value indicating whether the animation attached to the object with the specified key is paused.
  373. * @access public
  374. * @param {string} key - A string identifying an attached animation.
  375. * @returns {boolean} -
  376. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1523703-isanimationpaused
  377. */
  378. isAnimationPausedForKey(key) {
  379. return false
  380. }
  381.  
  382. // Instance Methods
  383.  
  384. /**
  385. * Required.
  386. * @access public
  387. * @param {number} speed -
  388. * @param {string} key -
  389. * @returns {void}
  390. * @see https://developer.apple.com/documentation/scenekit/scnanimatable/1778343-setanimationspeed
  391. */
  392. setAnimationSpeedForKey(speed, key) {
  393. }
  394.  
  395. /**
  396. * @access private
  397. * @param {WebGLContext} gl -
  398. * @returns {number} -
  399. */
  400. _wrapSFor(gl) {
  401. switch(this.wrapS){
  402. case SCNWrapMode.clamp:
  403. return gl.CLAMP_TO_EDGE // FIXME: do not apply the texture out of 0-1
  404. case SCNWrapMode.repeat:
  405. return gl.REPEAT
  406. case SCNWrapMode.clampToBorder:
  407. return gl.CLAMP_TO_EDGE
  408. case SCNWrapMode.mirror:
  409. return gl.MIRRORED_REPEAT
  410. default:
  411. throw new Error(`unknown wrapS: ${this.wrapS}`)
  412. }
  413. }
  414.  
  415. /**
  416. * @access private
  417. * @param {WebGLContext} gl -
  418. * @returns {number} -
  419. */
  420. _wrapTFor(gl) {
  421. switch(this.wrapT){
  422. case SCNWrapMode.clamp:
  423. return gl.CLAMP_TO_EDGE // FIXME: do not apply the texture out of 0-1
  424. case SCNWrapMode.repeat:
  425. return gl.REPEAT
  426. case SCNWrapMode.clampToBorder:
  427. return gl.CLAMP_TO_EDGE
  428. case SCNWrapMode.mirror:
  429. return gl.MIRRORED_REPEAT
  430. default:
  431. throw new Error(`unknown wrapT: ${this.wrapT}`)
  432. }
  433. }
  434.  
  435. /**
  436. * @access private
  437. * @param {WebGLContext} gl -
  438. * @returns {number} -
  439. */
  440. _minificationFilterFor(gl) {
  441. switch(this.minificationFilter){
  442. case SCNFilterMode.none:
  443. case SCNFilterMode.linear: {
  444. switch(this.mipFilter){
  445. case SCNFilterMode.none:
  446. return gl.LINEAR
  447. case SCNFilterMode.nearest:
  448. return gl.LINEAR_MIPMAP_NEAREST
  449. case SCNFilterMode.linear:
  450. return gl.LINEAR_MIPMAP_LINEAR
  451. default:
  452. throw new Error(`unknown mipmapFilter: ${this.mipmapFilter}`)
  453. }
  454. }
  455. case SCNFilterMode.nearest: {
  456. switch(this.mipFilter){
  457. case SCNFilterMode.none:
  458. return gl.NEAREST
  459. case SCNFilterMode.nearest:
  460. return gl.NEAREST_MIPMAP_NEAREST
  461. case SCNFilterMode.linear:
  462. return gl.NEAREST_MIPMAP_LINEAR
  463. default:
  464. throw new Error(`unknown mipmapFilter: ${this.mipmapFilter}`)
  465. }
  466. }
  467. default:
  468. throw new Error(`unknown minificationFilter: ${this.minificationFilter}`)
  469. }
  470. }
  471.  
  472. /**
  473. * @access private
  474. * @param {WebGLContext} gl -
  475. * @returns {number} -
  476. */
  477. _magnificationFilterFor(gl) {
  478. switch(this.magnificationFilter){
  479. case SCNFilterMode.none:
  480. return gl.LINEAR // default value
  481. case SCNFilterMode.nearest:
  482. return gl.NEAREST
  483. case SCNFilterMode.linear:
  484. return gl.LINEAR
  485. default:
  486. throw new Error(`unknown magnificationFilter: ${this.magnificationFilter}`)
  487. }
  488. }
  489.  
  490. /**
  491. * @access private
  492. * @param {string} path -
  493. * @param {string} dirPath -
  494. * @returns {Image} -
  495. */
  496. _loadContentsImage(path, dirPath) {
  497. const image = new Image()
  498. // TODO: check option if it allows cross-domain.
  499. image.crossOrigin = 'anonymous'
  500.  
  501. let __path = path
  502. if(__path.indexOf('file:///') === 0){
  503. __path = __path.slice(8)
  504. }
  505. // TODO: load OpenEXR File
  506. __path = __path.replace(/\.exr$/, '.png')
  507.  
  508. this._loadedPromise = new Promise((resolve, reject) => {
  509. const paths = __path.split('/')
  510. let pathCount = 1
  511. let _path = dirPath + paths.slice(-pathCount).join('/')
  512. image.onload = () => {
  513. this._contents = image
  514. resolve()
  515. }
  516. image.onerror = () => {
  517. pathCount += 1
  518. if(pathCount > paths.length){
  519. // try the root path
  520. image.onerror = () => {
  521. // give up
  522. reject()
  523. throw new Error(`image ${path} load error.`)
  524. }
  525. image.src = __path
  526. }else{
  527. // retry
  528. _path = dirPath + paths.slice(-pathCount).join('/')
  529. image.src = _path
  530. }
  531. }
  532. image.src = _path
  533. })
  534. return image
  535. }
  536.  
  537. /**
  538. * @access public
  539. * @returns {Float32Array} -
  540. */
  541. float32Array() {
  542. const target = this.__presentation ? this.__presentation : this
  543. if(_InstanceOf(target._contents, SKColor)){
  544. return target._contents.float32Array()
  545. //return target._contents.srgbToLinear().float32Array()
  546. }
  547. return new Float32Array([1, 1, 1, 1])
  548. }
  549.  
  550. /**
  551. * @access private
  552. * @returns {Promise} -
  553. */
  554. _getLoadedPromise() {
  555. if(this._loadedPromise){
  556. return this._loadedPromise
  557. }
  558. return Promise.resolve()
  559. }
  560.  
  561. /**
  562. * @access public
  563. * @type {Promise} -
  564. */
  565. get didLoad() {
  566. return this._getLoadedPromise()
  567. }
  568. }
  569.