diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 893374c0a7c..1e60107c697 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ ### Creator version?(版本号) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0f7b2159600..7ab44b0d7f1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ -Re: cocos-creator/fireball# +Re: cocos-creator/2d-tasks# -Changelog: +Changes: * \ No newline at end of file +--> diff --git a/.gitignore b/.gitignore index a3c739bd6c5..845739f582d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ node_modules /.project npm-debug.log DebugInfos.json +cocos2d/core/platform/deserialize-compiled.js +cocos2d/core/value-types/*.js diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6c0475d26c6..57656d09073 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -323,7 +323,7 @@ Cocos2d-JS v3.2 @ Dec.29, 2014 * Replaced `transform` function with `setTransform` function under canvas render mode for better performance. * Added a timer in `cc.audioEngine` to check audio element loading event, prevent the loading process being stucked when load audio file failed. * Added some new browser types to `cc.sys`. -* Added some audio resource loading codes to ensure compatibility with Wechat browser. +* Added some audio resource loading codes to ensure compatibility with WeChat browser. * Added check for WebAudio support to ensure compatibility. * Bug fixes: diff --git a/EngineErrorMap.md b/EngineErrorMap.md index 51f63c1de1a..2c88c8ece77 100644 --- a/EngineErrorMap.md +++ b/EngineErrorMap.md @@ -131,6 +131,10 @@ cc.ReverseTime.initWithAction(): the action was already passed in. cc.Animate.initWithAnimation(): animation must be non-NULL +### 1031 + +Illegal parameter get passed in cc.tween: %s. + ### 1100 Expected 'data' dict, but not found. Config file: %s @@ -174,7 +178,7 @@ loadScene: Unknown name type to load: '%s' ### 1208 -loadScene: Failed to load scene '%s' because '%s' is already loading +loadScene: Failed to load scene '%s' because '%s' is already being loaded. ### 1209 @@ -218,14 +222,17 @@ element type is wrong! ### 1401 + The first argument should be the destination object ### 1402 + The 'visible' property of %s is deprecated, use 'enabled' instead please. ### 1403 + Sorry, cc.audioEngine.willPlayMusic is removed. ### 1404 @@ -234,7 +241,7 @@ cc.spriteFrameCache is removed, please use cc.loader to load and cache sprite fr ### 1405 -The '%s' will be removed in v2.0, please use '%s' instead. +The '%s' has been removed, please use '%s' instead. ### 1406 @@ -242,7 +249,12 @@ The '%s' will be removed in v2.0, please use '%s' instead. ### 1407 -cc.pool is being removed from v2.0, you are getting cc.js.Pool instead + +cc.pool has been removed, you are getting cc.js.Pool instead. + +### 1408 + +'%s' is not support in the '%s', please use '%s' instead. ### 1500 @@ -276,7 +288,7 @@ warning: you CANNOT change update priority in scheduled function ### 1507 -CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f +CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %s to %s" ### 1508 @@ -296,6 +308,7 @@ cc.Scheduler: pause state of the scheduled task doesn't match the element pause ### 1512 + cc.Scheduler: updateFunc parameter is deprecated in scheduleUpdate function, and will be removed in v2.0 ### 1513 @@ -304,18 +317,22 @@ cc.Scheduler: scheduler stopped using `__instanceId` as id since v2.0, you shoul ### 1600 + getZOrder is deprecated. Please use getLocalZOrder instead. ### 1601 + setZOrder is deprecated. Please use setLocalZOrder instead. ### 1602 + RotationX != RotationY. Don't know which one to return ### 1603 + ScaleX != ScaleY. Don't know which one to return ### 1604 @@ -333,14 +350,17 @@ child must be non-null ### 1607 + removeFromParentAndCleanup is deprecated. Use removeFromParent instead ### 1608 + boundingBox is deprecated. Use getBoundingBox instead ### 1609 + argument tag is an invalid tag ### 1610 @@ -362,10 +382,12 @@ cc.Node.getActionByTag(): argument tag is an invalid tag ### 1614 + resumeSchedulerAndActions is deprecated, please use resume instead. ### 1615 + pauseSchedulerAndActions is deprecated, please use pause instead. ### 1616 @@ -467,6 +489,10 @@ Node's zIndex value can't be smaller than cc.macro.MIN_ZINDEX, setting to the mi Private node's zIndex can't be set, it will keep cc.macro.MIN_ZINDEX as its value +### 1639 + +cc.Action is deprecated, please use cc.Tween instead + ### 1700 @@ -532,7 +558,7 @@ cc.Layer.addLayer(): layer should be non-null ### 2200 -Resolution not valid +Design resolution not valid ### 2201 @@ -621,6 +647,7 @@ cc.Sprite.setDisplayFrameWithAnimationName(): Invalid frame index ### 2604 + setDisplayFrame is deprecated, please use setSpriteFrame instead. ### 2605 @@ -780,6 +807,7 @@ cc.SpriteBatchNode.addChild(): cc.SpriteBatchNode only supports cc.Sprites as ch ### 2710 + Sprite.initWithTexture(): Argument must be non-nil ### 2711 @@ -933,6 +961,7 @@ TextureCache:addPVRTCImage does not support on HTML5 ### 3002 + textureForKey is deprecated. Please use getTextureForKey instead. ### 3003 @@ -1054,12 +1083,21 @@ Mimpap texture only works in POT textures ### 3118 + contentSize parameter is deprecated and ignored for cc.Texture2D initWithData function. ### 3119 Lazy init texture with image element failed due to image loading failure: %s +### 3120 + +Loading texture with unsupported type: '%s'. Add '%s' into 'cc.macro.SUPPORT_TEXTURE_FORMATS' please. + +### 3121 + +Can't find a texture format supported by the current platform! Please add a fallback format in the editor. + ### 3200 @@ -1073,6 +1111,10 @@ Rect width exceeds maximum margin: %s Rect height exceeds maximum margin: %s +### 3401 + +Set texture with a url of image is not supported anymore. Please use cc.assetManager.loadRemote to load a texture first. + ### 3500 0 priority is forbidden for fixed priority since it's used for scene graph based priority. @@ -1239,6 +1281,7 @@ Should not add %s to a node which size is already used by its other component. ### 3629 + attribute must be type object ### 3630 @@ -1335,7 +1378,9 @@ Can not call `_super` or `prototype.ctor` in ES6 Classes "%s", use `super` inste ### 3652 -Failed to construct a dummy instance of the "%s" class using `new` behind the scenes. This is for getting default values declared in TypeScript. Please ensure the class will be able to construct during script's initialization. %s. +Failed to `new %s()` under the hood, %s +It is used for getting default values declared in TypeScript in the first place. +Please ensure the constructor can be called during the script's initialization. ### 3653 @@ -1344,27 +1389,27 @@ Default value must be initialized at their declaration: ``` // Before: @property({ - type: cc.Integer - default: 0 // <-- + type: cc.SpriteFrame + default: null // <-- }) -value; +myProp; // After: @property({ - type: cc.Integer + type: cc.SpriteFrame }) -value = 0; // <-- +myProp = null; // <-- ``` ### 3654 -Please specifiy a default value for "%s" property at its declaration: +Please specifiy a default value for "%s.%s" at its declaration: ``` // Before: @property(...) -value; +myProp; // After: @property(...) -value = 0 +myProp = 0; ``` ### 3655 @@ -1384,21 +1429,24 @@ set %s (value) { ### 3656 -The default value of %s.%s must be an empty string. (changed since 1.8) + +The default value of %s.%s must be an empty string. ### 3657 + The value assigned to %s should be Texture2D object, not url string. Since 1.8, you can declare a texture object directly in properties by using: ``` { default: null, - type: cc.Texture2D // use 'type:' instead of 'url:' + type: cc.Texture2D, // use 'type:' instead of 'url:' } ``` ### 3658 + browser does not support getters ### 3700 @@ -1531,6 +1579,7 @@ animator not added or already removed ### 3908 + animation not added or already removed ### 3909 @@ -1554,6 +1603,7 @@ already-playing ### 4000 + Sorry, the cc.Font has been modified from Raw Asset to Asset. Please load the font asset before using. ### 4001 @@ -1618,6 +1668,7 @@ Sorry, lineHeight of system font not supported on JSB. ### 4100 + Property padding is deprecated, please use paddingLeft, paddingRight, paddingTop and paddingBottom instead ### 4200 @@ -1662,6 +1713,7 @@ No need to release non-cached asset. ### 4903 + Can not get class '%s' ### 4904 @@ -1743,7 +1795,8 @@ cc.LabelBMFont._parseImageFileName() : file could not be found ### 4920 -Sorry, you shouldn't use id as item identity any more, please use url or uuid instead, the current id is being set as url: (%s) + +Sorry, you shouldn't use id as item identity anymore, please use url or uuid instead, the current id is being set as url: (%s) ### 4921 @@ -1787,6 +1840,7 @@ Load image ( %s ) failed ### 4931 + Download Uuid: can not find type of raw asset[ %s ]: %s ### 4932 @@ -1833,12 +1887,22 @@ Can not find script '%s' Can not find class '%s' +### 5303 + +Failed to deserialize %s, missing _deserialize function. + +### 5304 + +Unable to deserialize version %s data. + ### 5400 + '%s' is deprecated, use '%s' instead please. ### 5401 + '%s' is deprecated, use '%s' instead please. ### 5402 @@ -1863,30 +1927,35 @@ Class should be extended before assigning any prototype members. ### 5500 -'notify' can't work with 'get/set' ! +'notify' can not be used in 'get/set' ! ### 5501 -'notify' must work with 'default' ! +'notify' must be used with 'default' ! ### 5502 + Invalid url of %s.%s ### 5503 + The 'url' attribute of '%s.%s' is undefined when loading script. ### 5504 + The 'url' type of '%s.%s' must be child class of cc.RawAsset. ### 5505 + The 'url' type of '%s.%s' must not be child class of cc.Asset, otherwise you should use 'type: %s' instead. ### 5506 + Can not specify 'type' attribute for '%s.%s', because its 'url' is already defined. ### 5507 @@ -1904,7 +1973,7 @@ The 'type' attribute of '%s.%s' must be child class of cc.Asset, otherwise you s ### 5510 -The 'type' attribute of '%s.%s' can not be 'Number', use 'Float' or 'Integer' instead please. +The 'type' attribute of '%s.%s' can not be 'Number', use cc.Float or cc.Integer instead please. ### 5511 @@ -1936,6 +2005,7 @@ Property '%s.%s' must define at least one of 'default', 'get' or 'set'. ### 5600 + Argument must be non-nil ### 5601 @@ -2115,15 +2185,15 @@ Unkown error ### 6030 -cc.ParticleSystem: error decoding or ungzipping textureImageData +cc.ParticleSystem: error decoding or ungzipping textureImageData in plist '%s' ### 6031 -cc.ParticleSystem: unknown image format with Data +cc.ParticleSystem: unknown image format with Data in plist %s ### 6032 -cc.ParticleSystem.initWithDictionary() : error loading the texture +cc.ParticleSystem.initWithDictionary() : error loading the texture in plist '%s' ### 6100 @@ -2132,18 +2202,22 @@ Not supported file types, Please try use the ccs.load ### 6200 + Canvas doesn't support mesh slot! ### 6300 + only cc.DrawNode is accepted as stencil ### 6301 + Stencil buffer is not enabled. ### 6302 + Nesting more than %d stencils is not supported. Everything will be drawn without stencil for this node and its children. ### 6400 @@ -2152,6 +2226,7 @@ asset.url is not usable in core process ### 6401 + asset.urls is not usable in core process ### 6402 @@ -2172,26 +2247,31 @@ Can't find testFunc for (%s, $s). ### 6700 -Can't init canvas '%s' because it conflicts with the existing '%s', the scene should only have one active canvas at the same time +Can't init canvas '%s' because it conflicts with the existing '%s', the scene should only have one active canvas at the same time. ### 6701 + Should not add Canvas to a node which already contains a renderer component (%s). ### 6702 + Should not add Canvas to a node which size is already used by its other component. ### 6703 -Can't initialise DrawingPrimitiveWebGL. context need is WebGLRenderingContext + +Can't initialise DrawingPrimitiveWebGL. context need is WebGLRenderingContext. ### 6704 + Polygon's point must greater than 2 ### 6705 + Argument must be non-nil ### 6800 @@ -2228,18 +2308,22 @@ Can not instantiate DOM element ### 7000 + Failed to init asset's raw path. ### 7001 + Should not load '%s' from script dynamically, unless it is placed in the 'resources' folder. ### 7002 + Sorry can not load '%s' because it is not placed in the 'resources' folder. ### 7003 + Failed to init builtin asset's raw path. ### 7100 @@ -2316,6 +2400,7 @@ _ccsg.TMXTiledMap.initWithXML(): Map not found. Please check the filename. ### 7214 + propertiesForGID is deprecated. Please use getPropertiesForGID instead. ### 7215 @@ -2486,6 +2571,7 @@ Can not render dynamic created SkeletonData ### 7505 + Invalid type of atlasFile, atlas should be registered as raw asset. ### 7506 @@ -2626,15 +2712,18 @@ Missing StripByteCounts! ### 8100 + cocos2d: ERROR: Failed to compile shader: - %s +%s ### 8101 + cocos2d: ERROR: Failed to compile vertex shader ### 8102 + cocos2d: ERROR: Failed to compile fragment shader ### 8103 @@ -2691,4 +2780,40 @@ Stencil manager does not support level bigger than %d in this device. ### 9001 -Stencil manager is already empty, cannot pop any mask \ No newline at end of file +Stencil manager is already empty, cannot pop any mask. + +### 9100 + +LabelAtlas '%s' cannot be loaded, raw texture does not exist. + +### 9101 + +LabelAtlas '%s' cannot be loaded, fnt data does not exist. + +### 9102 + +Program not support highp precision, will change to mediump. + +### 9103 + +%s : Failed to set property [%s], property not found. + +### 9104 + +%s : Failed to define [%s], define not found. + +### 9105 + +%s : Failed to set property [%s], property length not correct. + +### 9106 + +%s : Set property [%s] warning : should transform object to ArrayBuffer. + +### 9107 + +%s : illegal property: [%s], myabe defined an unused property; + +### 9200 + +cc.view.enableAntiAlias is deprecated, please use cc.Texture2D.setFilters instead diff --git a/README.md b/README.md index 96f8c0828d0..6b89302f44a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,40 @@ -# Cocos Creator Engine Framework - -This repo is the engine framework for Cocos Creator, Cocos Creator is a game development tool focused on content creation, which has realized features like thorough scriptability, componentization and data driven, etc. on the basis of Cocos2d-x. - -Cocos Creator's in-editor scene view and web runtime share the same framework, which is the content of this repo. It's originally forked from [Cocos2d-html5](https://github.com/cocos2d/cocos2d-html5/), we build up an Entity Component architecture on it to meet the needs of Cocos Creator. - -This framework is a cross-platform game engine written in Javascript and licensed under MIT. It supports major desktop and mobile browsers, it's also compatible with [Cocos2d Javascript Binding engine](https://github.com/cocos-creator/cocos2d-x-lite) to support native platforms like iOS, Android, Win32, Mac OS X. +

+ + + +

+

+ + stars + + + forks + + + version + + + license + + + twitter + +

+ +# Cocos Creator + +![2.2.0 Main Window](https://user-images.githubusercontent.com/1503156/67261891-3cfdfb00-f4d5-11e9-9b2d-15ff2cb015f4.png) + +Cocos Creator is a complete package of game development tools and workflow, including a game engine, resource management, scene editing, game preview, debug and publish one project to multiple platforms. Cocos Creator focused on content creation, which has realized features like thorough scriptability, componentization and data driven, etc. on the basis of Cocos2d-x. With JavaScript, you can scripting your component in no time. The editor and engine extension is also made with JavaScript so you can make games and refine your tool in a single programming language. Cocos Creator is an provides an innovative, easy to use toolset such as the UI system and Animation editor. The toolset will be expanding continuously and quickly, thanks to the open editor extension system. + +This repo is the engine framework for Cocos Creator. Cocos Creator's in-editor scene view and web runtime share the same framework, which is the content of this repo. It's originally forked from [Cocos2d-html5](https://github.com/cocos2d/cocos2d-html5/), we build up an Entity Component architecture on it to meet the needs of Cocos Creator. + +This framework is a cross-platform game engine written in JavaScript and licensed under MIT. It supports major desktop and mobile browsers, it's also compatible with [Cocos2d Javascript Binding engine](https://github.com/cocos-creator/cocos2d-x-lite) to support native platforms like iOS, Android, Win32, macOS. The framework is naturally integrated with Cocos Creator, so it's not designed to be used independently. @@ -33,73 +63,77 @@ This is all you have to do to set engine development environment. gulp build ``` +If the compilation process encounters a "JavaScript heap out memory" warning, you can use the following command line + +```bash +gulp build --max-old-space-size=8192 +``` + ### Test #### Prerequisite - - Install [express](http://expressjs.com/): `npm install express` - - Install gulp-qunit: `npm install gulp-qunit` +- Install [express](http://expressjs.com/): `npm install express`. +- Install gulp-qunit: `npm install gulp-qunit`. #### Unit Test -##### Test in CLI +- Test in CLI -```bash -npm test -``` + ```bash + npm test + ``` -##### Test in browser +- Test in browser -1. Build for testing.
+ 1. Build for testing. - ```bash - gulp build-test - ``` + ```bash + gulp build-test + ``` -2. Start express in cloned project folder. + 2. Start express in cloned project folder. - ``` - node test/qunit/server.js - ``` + ```bash + node test/qunit/server.js + ``` -3. Open [http://localhost:8511/bin/qunit-runner.html](http://localhost:8511/bin/qunit-runner.html) in your browser. + 3. Open [http://localhost:8511/bin/qunit-runner.html](http://localhost:8511/bin/qunit-runner.html) in your browser. ### DebugInfos View [EngineErrorMap.md](https://github.com/cocos-creator/engine/blob/master/EngineErrorMap.md) All the debug infos are defined in file EngineErrorMap.md. -The file DebugInfos.json will be generated based on EngineErrorMap.md, when run gulp build* command. +The file DebugInfos.json will be generated based on EngineErrorMap.md, when run `gulp build` command. For details below: 1. Define log in EngineErrorMap.md - example + example: + ``` ### 1001 - cocos2d: removeAction: Target not found - + cocos2d: removeAction: Target not found ``` -2. Define deprecated log in EngineErrorMap.md +2. Define deprecated log in EngineErrorMap.md The log should be marked as DEPRECATED when then logId is no longer referenced in the project. - example + example: + ``` ### 1000 cc.ActionManager.addAction(): action must be non-null - ``` +## Useful links -## Links - -* [Official site](http://cocos2d-x.org/creator) -* [Download](http://cocos2d-x.org/download) -* [Documentation](http://www.cocos2d-x.org/docs/creator/manual/en/) -* [API References](http://www.cocos2d-x.org/docs/creator/api/en/) -* [Forum](http://discuss.cocos2d-x.org/c/editors-and-tools/cocos-creator) -* [Road Map](https://trello.com/b/JWVRRxMG/cocos-creator-roadmap) +* [Official site](https://www.cocos.com/products#CocosCreator) +* [Download](https://www.cocos.com/creator) +* [Documentation](https://docs.cocos.com/creator/manual/) +* [API References](https://docs.cocos.com/creator/api/) +* [Forum](https://discuss.cocos2d-x.org/c/creator) diff --git a/api.d.ts b/api.d.ts new file mode 100644 index 00000000000..e8e3210ac41 --- /dev/null +++ b/api.d.ts @@ -0,0 +1,32 @@ + +declare let CC_JSB: boolean +declare let CC_NATIVERENDERER: boolean +declare let CC_EDITOR: boolean +declare let CC_PREVIEW: boolean +declare let CC_TEST: boolean +declare let CC_DEBUG: boolean + +declare let cc: { + // polyfills: { + // destroyObject? (object: any): void; + // }; + [x: string]: any; +} + +declare let Editor: any; + +// https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c +type FlagExcludedType = { [Key in keyof Base]: Base[Key] extends Type ? never : Key }; +type AllowedNames = FlagExcludedType[keyof Base]; +type KeyPartial = { [P in K]?: T[P] }; +type OmitType = KeyPartial>; +type ConstructorType = OmitType; + +declare interface IWritableArrayLike { + readonly length: number; + [index: number]: T; +} + +declare let module: { + exports: object +} diff --git a/cocos2d/actions/CCActionEase.js b/cocos2d/actions/CCActionEase.js index bcc3cb2f1f2..4f3e681a291 100644 --- a/cocos2d/actions/CCActionEase.js +++ b/cocos2d/actions/CCActionEase.js @@ -543,6 +543,9 @@ cc.easeBackIn = function(){ */ var _easeBackOutObj = { easing: function (time1) { + if (time1 === 0) { + return 0; + } var overshoot = 1.70158; time1 = time1 - 1; return time1 * time1 * ((overshoot + 1) * time1 + overshoot) + 1; diff --git a/cocos2d/actions/CCActionInstant.js b/cocos2d/actions/CCActionInstant.js index 1ff2aa5c85c..518d2c8da23 100644 --- a/cocos2d/actions/CCActionInstant.js +++ b/cocos2d/actions/CCActionInstant.js @@ -239,6 +239,44 @@ cc.removeSelf = function(isNeedCleanUp){ return new cc.RemoveSelf(isNeedCleanUp); }; +/* + * Create an action to destroy self. + * @class DestroySelf + * @extends ActionInstant + * + * @example + * var destroySelfAction = new cc.DestroySelf(); + */ +cc.DestroySelf = cc.Class({ + name: 'cc.DestroySelf', + extends: cc.ActionInstant, + + update () { + this.target.destroy(); + }, + + reverse () { + return new cc.DestroySelf(); + }, + + clone () { + return new cc.DestroySelf(); + } +}); + +/** + * !#en Destroy self + * !#zh 创建一个销毁自身的动作。 + * @method destroySelf + * @return {ActionInstant} + * + * @example + * var destroySelfAction = cc.destroySelf(); + */ +cc.destroySelf = function () { + return new cc.DestroySelf(); +}; + /* * Flips the sprite horizontally. * @class FlipX diff --git a/cocos2d/actions/CCActionInterval.js b/cocos2d/actions/CCActionInterval.js index 14eb155191c..e1c7107c74c 100644 --- a/cocos2d/actions/CCActionInterval.js +++ b/cocos2d/actions/CCActionInterval.js @@ -25,6 +25,7 @@ THE SOFTWARE. ****************************************************************************/ + /** * @module cc */ @@ -839,7 +840,7 @@ cc.Spawn = cc.Class({ * @example * // example * var action = cc.spawn(cc.jumpBy(2, cc.v2(300, 0), 50, 4), cc.rotateBy(2, 720)); - * todo:It should be the direct use new + * todo: It should be the direct use new */ cc.spawn = function (/*Multiple Arguments*/tempArray) { var paramArray = (tempArray instanceof Array) ? tempArray : arguments; @@ -866,13 +867,12 @@ cc.Spawn._actionOneTwo = function (action1, action2) { /* - * Rotates a Node object to a certain angle by modifying its rotation property.
+ * Rotates a Node object to a certain angle by modifying its angle property.
* The direction will be decided by the shortest angle. * @class RotateTo * @extends ActionInterval * @param {Number} duration duration in seconds - * @param {Number} deltaAngleX deltaAngleX in degrees. - * @param {Number} [deltaAngleY] deltaAngleY in degrees. + * @param {Number} dstAngle dstAngle in degrees. * @example * var rotateTo = new cc.RotateTo(2, 61.0); */ @@ -880,27 +880,26 @@ cc.RotateTo = cc.Class({ name: 'cc.RotateTo', extends: cc.ActionInterval, - ctor:function (duration, deltaAngleX, deltaAngleY) { - this._dstAngleX = 0; - this._startAngleX = 0; - this._diffAngleX = 0; - this._dstAngleY = 0; - this._startAngleY = 0; - this._diffAngleY = 0; - deltaAngleX !== undefined && this.initWithDuration(duration, deltaAngleX, deltaAngleY); + statics: { + _reverse: false, + }, + + ctor:function (duration, dstAngle) { + this._startAngle = 0; + this._dstAngle = 0; + this._angle = 0; + dstAngle !== undefined && this.initWithDuration(duration, dstAngle); }, /* * Initializes the action. * @param {Number} duration - * @param {Number} deltaAngleX - * @param {Number} deltaAngleY + * @param {Number} dstAngle * @return {Boolean} */ - initWithDuration:function (duration, deltaAngleX, deltaAngleY) { + initWithDuration:function (duration, dstAngle) { if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) { - this._dstAngleX = deltaAngleX || 0; - this._dstAngleY = deltaAngleY !== undefined ? deltaAngleY : this._dstAngleX; + this._dstAngle = dstAngle; return true; } return false; @@ -909,31 +908,21 @@ cc.RotateTo = cc.Class({ clone:function () { var action = new cc.RotateTo(); this._cloneDecoration(action); - action.initWithDuration(this._duration, this._dstAngleX, this._dstAngleY); + action.initWithDuration(this._duration, this._dstAngle); return action; }, startWithTarget:function (target) { cc.ActionInterval.prototype.startWithTarget.call(this, target); - // Calculate X - var locStartAngleX = target.rotationX % 360.0; - var locDiffAngleX = this._dstAngleX - locStartAngleX; - if (locDiffAngleX > 180) - locDiffAngleX -= 360; - if (locDiffAngleX < -180) - locDiffAngleX += 360; - this._startAngleX = locStartAngleX; - this._diffAngleX = locDiffAngleX; - - // Calculate Y It's duplicated from calculating X since the rotation wrap should be the same - this._startAngleY = target.rotationY % 360.0; - var locDiffAngleY = this._dstAngleY - this._startAngleY; - if (locDiffAngleY > 180) - locDiffAngleY -= 360; - if (locDiffAngleY < -180) - locDiffAngleY += 360; - this._diffAngleY = locDiffAngleY; + let startAngle = target.angle % 360; + + let angle = cc.RotateTo._reverse ? (this._dstAngle - startAngle) : (this._dstAngle + startAngle); + if (angle > 180) angle -= 360; + if (angle < -180) angle += 360; + + this._startAngle = startAngle; + this._angle = cc.RotateTo._reverse ? angle : -angle; }, reverse:function () { @@ -943,39 +932,36 @@ cc.RotateTo = cc.Class({ update:function (dt) { dt = this._computeEaseTime(dt); if (this.target) { - this.target.rotationX = this._startAngleX + this._diffAngleX * dt; - this.target.rotationY = this._startAngleY + this._diffAngleY * dt; + this.target.angle = this._startAngle + this._angle * dt; } } }); /** * !#en - * Rotates a Node object to a certain angle by modifying its rotation property.
+ * Rotates a Node object to a certain angle by modifying its angle property.
* The direction will be decided by the shortest angle. - * !#zh 旋转到目标角度,通过逐帧修改它的 rotation 属性,旋转方向将由最短的角度决定。 + * !#zh 旋转到目标角度,通过逐帧修改它的 angle 属性,旋转方向将由最短的角度决定。 * @method rotateTo * @param {Number} duration duration in seconds - * @param {Number} deltaAngleX deltaAngleX in degrees. - * @param {Number} [deltaAngleY] deltaAngleY in degrees. + * @param {Number} dstAngle dstAngle in degrees. * @return {ActionInterval} * @example * // example * var rotateTo = cc.rotateTo(2, 61.0); */ -cc.rotateTo = function (duration, deltaAngleX, deltaAngleY) { - return new cc.RotateTo(duration, deltaAngleX, deltaAngleY); +cc.rotateTo = function (duration, dstAngle) { + return new cc.RotateTo(duration, dstAngle); }; /* - * Rotates a Node object clockwise a number of degrees by modifying its rotation property. + * Rotates a Node object clockwise a number of degrees by modifying its angle property. * Relative to its properties to modify. * @class RotateBy * @extends ActionInterval * @param {Number} duration duration in seconds - * @param {Number} deltaAngleX deltaAngleX in degrees - * @param {Number} [deltaAngleY] deltaAngleY in degrees + * @param {Number} deltaAngle deltaAngle in degrees * @example * var actionBy = new cc.RotateBy(2, 360); */ @@ -983,25 +969,27 @@ cc.RotateBy = cc.Class({ name: 'cc.RotateBy', extends: cc.ActionInterval, - ctor: function (duration, deltaAngleX, deltaAngleY) { - this._angleX = 0; - this._startAngleX = 0; - this._angleY = 0; - this._startAngleY = 0; - deltaAngleX !== undefined && this.initWithDuration(duration, deltaAngleX, deltaAngleY); + statics: { + _reverse: false, + }, + + ctor: function (duration, deltaAngle) { + deltaAngle *= cc.RotateBy._reverse ? 1 : -1; + + this._deltaAngle = 0; + this._startAngle = 0; + deltaAngle !== undefined && this.initWithDuration(duration, deltaAngle); }, /* * Initializes the action. * @param {Number} duration duration in seconds - * @param {Number} deltaAngleX deltaAngleX in degrees - * @param {Number} [deltaAngleY=] deltaAngleY in degrees + * @param {Number} deltaAngle deltaAngle in degrees * @return {Boolean} */ - initWithDuration:function (duration, deltaAngleX, deltaAngleY) { + initWithDuration:function (duration, deltaAngle) { if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) { - this._angleX = deltaAngleX || 0; - this._angleY = deltaAngleY !== undefined ? deltaAngleY : this._angleX; + this._deltaAngle = deltaAngle; return true; } return false; @@ -1010,26 +998,25 @@ cc.RotateBy = cc.Class({ clone:function () { var action = new cc.RotateBy(); this._cloneDecoration(action); - action.initWithDuration(this._duration, this._angleX, this._angleY); + action.initWithDuration(this._duration, this._deltaAngle); return action; }, startWithTarget:function (target) { cc.ActionInterval.prototype.startWithTarget.call(this, target); - this._startAngleX = target.rotationX; - this._startAngleY = target.rotationY; + this._startAngle = target.angle; }, update:function (dt) { dt = this._computeEaseTime(dt); if (this.target) { - this.target.rotationX = this._startAngleX + this._angleX * dt; - this.target.rotationY = this._startAngleY + this._angleY * dt; + this.target.angle = this._startAngle + this._deltaAngle * dt; } }, reverse:function () { - var action = new cc.RotateBy(this._duration, -this._angleX, -this._angleY); + var action = new cc.RotateBy(); + action.initWithDuration(this._duration, -this._deltaAngle); this._cloneDecoration(action); this._reverseEaseList(action); return action; @@ -1038,20 +1025,19 @@ cc.RotateBy = cc.Class({ /** * !#en - * Rotates a Node object clockwise a number of degrees by modifying its rotation property. + * Rotates a Node object clockwise a number of degrees by modifying its angle property. * Relative to its properties to modify. * !#zh 旋转指定的角度。 * @method rotateBy * @param {Number} duration duration in seconds - * @param {Number} deltaAngleX deltaAngleX in degrees - * @param {Number} [deltaAngleY] deltaAngleY in degrees + * @param {Number} deltaAngle deltaAngle in degrees * @return {ActionInterval} * @example * // example * var actionBy = cc.rotateBy(2, 360); */ -cc.rotateBy = function (duration, deltaAngleX, deltaAngleY) { - return new cc.RotateBy(duration, deltaAngleX, deltaAngleY); +cc.rotateBy = function (duration, deltaAngle) { + return new cc.RotateBy(duration, deltaAngle); }; @@ -2429,7 +2415,7 @@ cc.DelayTime = cc.Class({ /** * !#en Delays the action a certain amount of seconds. - * !#en 延迟指定的时间量。 + * !#zh 延迟指定的时间量。 * @method delayTime * @param {Number} d duration in seconds * @return {ActionInterval} diff --git a/cocos2d/actions/CCActionManager.js b/cocos2d/actions/CCActionManager.js index e634467081b..0b7b2f692dd 100644 --- a/cocos2d/actions/CCActionManager.js +++ b/cocos2d/actions/CCActionManager.js @@ -182,23 +182,37 @@ cc.ActionManager.prototype = { */ removeAction:function (action) { // explicit null handling - if (action == null) + if (!action) { return; + } var target = action.getOriginalTarget(); var element = this._hashTargets[target._id]; - if (element) { - for (var i = 0; i < element.actions.length; i++) { - if (element.actions[i] === action) { - element.actions.splice(i, 1); - // update actionIndex in case we are in tick. looping over the actions - if (element.actionIndex >= i) - element.actionIndex--; - break; + if (!element) { + return; + } + + for (var i = 0; i < element.actions.length; i++) { + if (element.actions[i] === action) { + element.actions.splice(i, 1); + // update actionIndex in case we are in tick. looping over the actions + if (element.actionIndex >= i) + element.actionIndex--; + break; + } + } + }, + + _removeActionByTag (tag, element, target) { + for (var i = 0, l = element.actions.length; i < l; ++i) { + var action = element.actions[i]; + if (action && action.getTag() === tag) { + if (target && action.getOriginalTarget() !== target) { + continue; } + this._removeActionAtIndex(i, element); + break; } - } else { - cc.logID(1001); } }, @@ -207,24 +221,23 @@ cc.ActionManager.prototype = { * !#zh 删除指定对象下特定标签的一个动作,将删除首个匹配到的动作。 * @method removeActionByTag * @param {Number} tag - * @param {Node} target + * @param {Node} [target] */ removeActionByTag:function (tag, target) { if(tag === cc.Action.TAG_INVALID) cc.logID(1002); - cc.assertID(target, 1003); - - var element = this._hashTargets[target._id]; - - if (element) { - var limit = element.actions.length; - for (var i = 0; i < limit; ++i) { - var action = element.actions[i]; - if (action && action.getTag() === tag && action.getOriginalTarget() === target) { - this._removeActionAtIndex(i, element); - break; - } + let hashTargets = this._hashTargets; + if (target) { + var element = hashTargets[target._id]; + if (element) { + this._removeActionByTag(tag, element, target); + } + } + else { + for (let name in hashTargets) { + let element = hashTargets[name]; + this._removeActionByTag(tag, element); } } }, diff --git a/cocos2d/actions/index.js b/cocos2d/actions/index.js index 52557a1c17f..606b22d126c 100644 --- a/cocos2d/actions/index.js +++ b/cocos2d/actions/index.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -28,4 +28,5 @@ require('./CCAction'); require('./CCActionInterval'); require('./CCActionInstant'); require('./CCActionEase'); -require('./CCActionCatmullRom'); \ No newline at end of file +require('./CCActionCatmullRom'); +require('./tween'); diff --git a/cocos2d/actions/tween.js b/cocos2d/actions/tween.js new file mode 100644 index 00000000000..03a497745a4 --- /dev/null +++ b/cocos2d/actions/tween.js @@ -0,0 +1,703 @@ +import { bezier } from '../animation/bezier'; + +let _tweenID = 0; + +let TweenAction = cc.Class({ + name: 'cc.TweenAction', + extends: cc.ActionInterval, + + ctor (duration, props, opts) { + this._opts = opts = opts || Object.create(null); + this._props = Object.create(null); + + // global easing or progress used for this action + opts.progress = opts.progress || this.progress; + if (opts.easing && typeof opts.easing === 'string') { + let easingName = opts.easing; + opts.easing = cc.easing[easingName]; + !opts.easing && cc.warnID(1031, easingName); + } + + let relative = this._opts.relative; + + for (let name in props) { + let value = props[name]; + + // property may have custom easing or progress function + let easing, progress; + if (value.value !== undefined && (value.easing || value.progress)) { + if (typeof value.easing === 'string') { + easing = cc.easing[value.easing]; + !easing && cc.warnID(1031, value.easing); + } + else { + easing = value.easing; + } + progress = value.progress; + value = value.value; + } + + let isNumber = typeof value === 'number'; + if (!isNumber && (!value.lerp || (relative && !value.add && !value.mul) || !value.clone)) { + cc.warn(`Can not animate ${name} property, because it do not have [lerp, (add|mul), clone] function.`); + continue; + } + + let prop = Object.create(null); + prop.value = value; + prop.easing = easing; + prop.progress = progress; + this._props[name] = prop; + } + + this._originProps = props; + this.initWithDuration(duration); + }, + + clone () { + var action = new TweenAction(this._duration, this._originProps, this._opts); + this._cloneDecoration(action); + return action; + }, + + startWithTarget (target) { + cc.ActionInterval.prototype.startWithTarget.call(this, target); + + let relative = !!this._opts.relative; + let props = this._props; + for (let name in props) { + let value = target[name]; + let prop = props[name]; + + if (typeof value === 'number') { + prop.start = value; + prop.current = value; + prop.end = relative ? value + prop.value : prop.value; + } + else { + prop.start = value.clone(); + prop.current = value.clone(); + prop.end = relative ? (value.add || value.mul).call(value, prop.value) : prop.value; + } + } + }, + + update (t) { + let opts = this._opts; + let easingTime = t; + if (opts.easing) easingTime = opts.easing(t); + + let target = this.target; + if (!target) return; + + let props = this._props; + let progress = this._opts.progress; + for (let name in props) { + let prop = props[name]; + let time = prop.easing ? prop.easing(t) : easingTime; + let current = prop.current = (prop.progress || progress)(prop.start, prop.end, prop.current, time); + target[name] = current; + } + }, + + progress (start, end, current, t) { + if (typeof start === 'number') { + current = start + (end - start) * t; + } + else { + start.lerp(end, t, current); + } + return current; + } +}); + +let SetAction = cc.Class({ + name: 'cc.SetAction', + extends: cc.ActionInstant, + + ctor (props) { + this._props = {}; + props !== undefined && this.init(props); + }, + + init (props) { + for (let name in props) { + this._props[name] = props[name]; + } + return true; + }, + + update () { + let props = this._props; + let target = this.target; + for (let name in props) { + target[name] = props[name]; + } + }, + + clone () { + var action = new SetAction(); + action.init(this._props); + return action; + } +}); + + + +/** + * !#en + * Tween provide a simple and flexible way to create action. Tween's api is more flexible than `cc.Action`: + * - Support creating an action sequence in chained api. + * - Support animate any objects' any properties, not limited to node's properties. By contrast, `cc.Action` needs to create a new action class to support new node property. + * - Support working with `cc.Action`. + * - Support easing and progress function. + * !#zh + * Tween 提供了一个简单灵活的方法来创建 action。相对于 Cocos 传统的 `cc.Action`,`cc.Tween` 在创建动画上要灵活非常多: + * - 支持以链式结构的方式创建一个动画序列。 + * - 支持对任意对象的任意属性进行缓动,不再局限于节点上的属性,而 `cc.Action` 添加一个属性的支持时还需要添加一个新的 action 类型。 + * - 支持与 `cc.Action` 混用。 + * - 支持设置 {{#crossLink "Easing"}}{{/crossLink}} 或者 progress 函数。 + * @class Tween + * @example + * cc.tween(node) + * .to(1, {scale: 2, position: cc.v3(100, 100, 100)}) + * .call(() => { console.log('This is a callback'); }) + * .by(1, {scale: 3, position: cc.v3(200, 200, 200)}, {easing: 'sineOutIn'}) + * .start(cc.find('Canvas/cocos')); + * @typescript Tween + */ +function Tween (target) { + this._actions = []; + this._finalAction = null; + this._target = target; + this._tag = cc.Action.TAG_INVALID; +} + +/** + * @method constructor + * @param {Object} [target] + */ + +/** + * !#en Stop all tweens + * !#zh 停止所有缓动 + * @method stopAll + * @static + */ +Tween.stopAll = function () { + cc.director.getActionManager().removeAllActions(); +} +/** + * !#en Stop all tweens by tag + * !#zh 停止所有指定标签的缓动 + * @method stopAllByTag + * @static + * @param {number} tag + */ +Tween.stopAllByTag = function (tag) { + cc.director.getActionManager().removeActionByTag(tag); +} +/** + * !#en Stop all tweens by target + * !#zh 停止所有指定对象的缓动 + * @method stopAllByTarget + * @static + * @param {Object} target + */ +Tween.stopAllByTarget = function (target) { + cc.director.getActionManager().removeAllActionsFromTarget(target); +} + +/** + * !#en + * Insert an action or tween to this sequence + * !#zh + * 插入一个 action 或者 tween 到队列中 + * @method then + * @param {Action|Tween} other + * @return {Tween} + * @typescript then(other: Action|Tween): Tween + */ +Tween.prototype.then = function (other) { + if (other instanceof cc.Action) { + this._actions.push(other.clone()); + } + else { + this._actions.push(other._union()); + } + return this; +}; + + +/** + * !#en + * Set tween target + * !#zh + * 设置 tween 的 target + * @method target + * @param {Object} target + * @return {Tween} + * @typescript target(target: any): Tween + */ +Tween.prototype.target = function (target) { + this._target = target; + return this; +}; + +/** + * !#en + * Start this tween + * !#zh + * 运行当前 tween + * @method start + * @return {Tween} + * @typescript start(): Tween + */ +Tween.prototype.start = function () { + let target = this._target; + if (!target) { + cc.warn('Please set target to tween first'); + return this; + } + if (target instanceof cc.Object && !target.isValid) { + return; + } + + if (this._finalAction) { + cc.director.getActionManager().removeAction(this._finalAction); + } + this._finalAction = this._union(); + + if (target._id === undefined) { + target._id = ++_tweenID; + } + + this._finalAction.setTag(this._tag); + cc.director.getActionManager().addAction(this._finalAction, target, false); + return this; +}; + +/** + * !#en + * Stop this tween + * !#zh + * 停止当前 tween + * @method stop + * @return {Tween} + * @typescript stop(): Tween + */ +Tween.prototype.stop = function () { + if (this._finalAction) { + cc.director.getActionManager().removeAction(this._finalAction); + } + return this; +}; + + +/** + * !#en Sets tween tag + * !#zh 设置缓动的标签 + * @method tag + * @param {number} tag + * @return {Tween} + * @typescript tag(tag: number): Tween + */ +Tween.prototype.tag = function (tag) { + this._tag = tag; + return this; +}; + + +/** + * !#en + * Clone a tween + * !#zh + * 克隆当前 tween + * @method clone + * @param {Object} [target] + * @return {Tween} + * @typescript clone(target?: any): Tween + */ +Tween.prototype.clone = function (target) { + let action = this._union(); + return cc.tween(target).then(action.clone()); +}; + +/** + * !#en + * Integrate all previous actions to an action. + * !#zh + * 将之前所有的 action 整合为一个 action。 + * @method union + * @return {Tween} + * @typescritp union(): Tween + */ +Tween.prototype.union = function () { + let action = this._union(); + this._actions.length = 0; + this._actions.push(action); + return this; +}; + +Tween.prototype._union = function () { + let actions = this._actions; + + if (actions.length === 1) { + actions = actions[0]; + } + else { + actions = cc.sequence(actions); + } + + return actions; +}; + +Object.assign(Tween.prototype, { + /** + * !#en Sets target's position property according to the bezier curve. + * !#zh 按照贝塞尔路径设置目标的 position 属性。 + * @method bezierTo + * @param {number} duration + * @param {cc.Vec2} c1 + * @param {cc.Vec2} c2 + * @param {cc.Vec2} to + * @return {Tween} + * @typescript bezierTo(duration: number, c1: Vec2, c2: Vec2, to: Vec2): Tween + */ + bezierTo (duration, c1, c2, to, opts) { + let c0x = c1.x, c0y = c1.y, + c1x = c2.x, c1y = c2.y; + opts = opts || Object.create(null); + opts.progress = function (start, end, current, t) { + current.x = bezier(start.x, c0x, c1x, end.x, t); + current.y = bezier(start.y, c0y, c1y, end.y, t); + return current; + } + return this.to(duration, { position: to }, opts); + }, + + /** + * !#en Sets target's position property according to the bezier curve. + * !#zh 按照贝塞尔路径设置目标的 position 属性。 + * @method bezierBy + * @param {number} duration + * @param {cc.Vec2} c1 + * @param {cc.Vec2} c2 + * @param {cc.Vec2} to + * @return {Tween} + * @typescript bezierBy(duration: number, c1: Vec2, c2: Vec2, to: Vec2): Tween + */ + bezierBy (duration, c1, c2, to, opts) { + let c0x = c1.x, c0y = c1.y, + c1x = c2.x, c1y = c2.y; + opts = opts || Object.create(null); + opts.progress = function (start, end, current, t) { + let sx = start.x, sy = start.y; + current.x = bezier(sx, c0x + sx, c1x + sx, end.x, t); + current.y = bezier(sy, c0y + sy, c1y + sy, end.y, t); + return current; + } + return this.by(duration, { position: to }, opts); + }, + + /** + * !#en Flips target's scaleX + * !#zh 翻转目标的 scaleX 属性 + * @method flipX + * @return {Tween} + * @typescript flipX(): Tween + */ + flipX () { + return this.call(() => { this._target.scaleX *= -1; }, this); + + }, + /** + * !#en Flips target's scaleY + * !#zh 翻转目标的 scaleY 属性 + * @method flipY + * @return {Tween} + * @typescript flipY(): Tween + */ + flipY () { + return this.call(() => { this._target.scaleY *= -1; }, this); + }, + + /** + * !#en Blinks target by set target's opacity property + * !#zh 通过设置目标的 opacity 属性达到闪烁效果 + * @method blink + * @param {number} duration + * @param {number} times + * @param {Object} [opts] + * @param {Function} [opts.progress] + * @param {Function|String} [opts.easing] + * @return {Tween} + * @typescript blink(duration: number, times: number, opts?: {progress?: Function; easing?: Function|string; }): Tween + */ + blink (duration, times, opts) { + var slice = 1.0 / times; + opts = opts || Object.create(null); + opts.progress = function (start, end, current, t) { + if (t >= 1) { + return start; + } + else { + var m = t % slice; + return (m > (slice / 2)) ? 255 : 0; + } + }; + return this.to(duration, { opacity: 1 }, opts); + }, +}) + +let tmp_args = []; + +function wrapAction (action) { + return function () { + tmp_args.length = 0; + for (let l = arguments.length, i = 0; i < l; i++) { + let arg = tmp_args[i] = arguments[i]; + if (arg instanceof Tween) { + tmp_args[i] = arg._union(); + } + } + + return action.apply(this, tmp_args); + }; +} + +let actions = { + /** + * !#en + * Add an action which calculate with absolute value + * !#zh + * 添加一个对属性进行绝对值计算的 action + * @method to + * @param {Number} duration + * @param {Object} props - {scale: 2, position: cc.v3(100, 100, 100)} + * @param {Object} [opts] + * @param {Function} [opts.progress] + * @param {Function|String} [opts.easing] + * @return {Tween} + * @typescript + * to > (duration: number, props: ConstructorType, opts?: OPTS) : Tween + */ + to (duration, props, opts) { + opts = opts || Object.create(null); + opts.relative = false; + return new TweenAction(duration, props, opts); + }, + + /** + * !#en + * Add an action which calculate with relative value + * !#zh + * 添加一个对属性进行相对值计算的 action + * @method by + * @param {Number} duration + * @param {Object} props - {scale: 2, position: cc.v3(100, 100, 100)} + * @param {Object} [opts] + * @param {Function} [opts.progress] + * @param {Function|String} [opts.easing] + * @return {Tween} + * @typescript + * by > (duration: number, props: ConstructorType, opts?: OPTS) : Tween + */ + by (duration, props, opts) { + opts = opts || Object.create(null); + opts.relative = true; + return new TweenAction(duration, props, opts); + }, + + /** + * !#en + * Directly set target properties + * !#zh + * 直接设置 target 的属性 + * @method set + * @param {Object} props + * @return {Tween} + * @typescript + * set (props: ConstructorType) : Tween + */ + set (props) { + return new SetAction(props); + }, + + /** + * !#en + * Add an delay action + * !#zh + * 添加一个延时 action + * @method delay + * @param {Number} duration + * @return {Tween} + * @typescript delay(duration: number): Tween + */ + delay: cc.delayTime, + /** + * !#en + * Add an callback action + * !#zh + * 添加一个回调 action + * @method call + * @param {Function} callback + * @return {Tween} + * @typescript call(callback: Function): Tween + */ + call: cc.callFunc, + /** + * !#en + * Add an hide action + * !#zh + * 添加一个隐藏 action + * @method hide + * @return {Tween} + * @typescript hide(): Tween + */ + hide: cc.hide, + /** + * !#en + * Add an show action + * !#zh + * 添加一个显示 action + * @method show + * @return {Tween} + * @typescript show(): Tween + */ + show: cc.show, + /** + * !#en + * Add an removeSelf action + * !#zh + * 添加一个移除自己 action + * @method removeSelf + * @return {Tween} + * @typescript removeSelf(): Tween + */ + removeSelf: cc.removeSelf, + /** + * !#en + * Add an sequence action + * !#zh + * 添加一个队列 action + * @method sequence + * @param {Action|Tween} action + * @param {Action|Tween} ...actions + * @return {Tween} + * @typescript sequence(action: Action|Tween, ...actions: (Action|Tween)[]): Tween + */ + sequence: wrapAction(cc.sequence), + /** + * !#en + * Add an parallel action + * !#zh + * 添加一个并行 action + * @method parallel + * @param {Action|Tween} action + * @param {Action|Tween} ...actions + * @return {Tween} + * @typescript parallel(action: Action|Tween, ...actions: (Action|Tween)[]): Tween + */ + parallel: wrapAction(cc.spawn) +}; + +// these action will use previous action as their parameters +let previousAsInputActions = { + /** + * !#en + * Add an repeat action. This action will integrate before actions to a sequence action as their parameters. + * !#zh + * 添加一个重复 action,这个 action 会将前一个动作作为他的参数。 + * @method repeat + * @param {Number} repeatTimes + * @param {Action | Tween} [action] + * @return {Tween} + * @typescript repeat(repeatTimes: number, action?: Action|Tween): Tween + */ + repeat: cc.repeat, + /** + * !#en + * Add an repeat forever action. This action will integrate before actions to a sequence action as their parameters. + * !#zh + * 添加一个永久重复 action,这个 action 会将前一个动作作为他的参数。 + * @method repeatForever + * @param {Action | Tween} [action] + * @return {Tween} + * @typescript repeatForever(action?: Action|Tween): Tween + */ + repeatForever: function (action) { + // TODO: fixed with cc.repeatForever + return cc.repeat(action, 10e8); + }, + /** + * !#en + * Add an reverse time action. This action will integrate before actions to a sequence action as their parameters. + * !#zh + * 添加一个倒置时间 action,这个 action 会将前一个动作作为他的参数。 + * @method reverseTime + * @param {Action | Tween} [action] + * @return {Tween} + * @typescript reverseTime(action?: Action|Tween): Tween + */ + reverseTime: cc.reverseTime, +}; + + +let keys = Object.keys(actions); +for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + Tween.prototype[key] = function () { + let action = actions[key].apply(this, arguments); + this._actions.push(action); + return this; + }; +} + +keys = Object.keys(previousAsInputActions); +for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + Tween.prototype[key] = function () { + + let actions = this._actions; + let action = arguments[arguments.length - 1]; + let length = arguments.length - 1; + + if (action instanceof cc.Tween) { + action = action._union(); + } + else if (!(action instanceof cc.Action)) { + action = actions[actions.length - 1]; + actions.length -= 1; + length += 1; + } + + let args = [action]; + for (let i = 0; i < length; i++) { + args.push(arguments[i]); + } + + action = previousAsInputActions[key].apply(this, args); + actions.push(action); + + return this; + }; +} + +/** + * @module cc + */ + +/** + * @method tween + * @param {Object} [target] - the target to animate + * @return {Tween} + * @typescript + * tween (target?: T) : Tween + */ +cc.tween = function (target) { + return new Tween(target); +}; + +cc.Tween = Tween; + \ No newline at end of file diff --git a/cocos2d/animation/animation-animator.js b/cocos2d/animation/animation-animator.js index 1dcf87b3fc0..52e28c47d42 100644 --- a/cocos2d/animation/animation-animator.js +++ b/cocos2d/animation/animation-animator.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -23,15 +23,11 @@ THE SOFTWARE. ****************************************************************************/ -var js = cc.js; -var Playable = require('./playable'); -var DynamicAnimCurve = require('./animation-curves').DynamicAnimCurve; -var quickFindIndex = require('./animation-curves').quickFindIndex; -var sampleMotionPaths = require('./motion-path-helper').sampleMotionPaths; -var EventAnimCurve = require('./animation-curves').EventAnimCurve; -var EventInfo = require('./animation-curves').EventInfo; -var WrapModeMask = require('./types').WrapModeMask; -var binarySearch = require('../core/utils/binary-search').binarySearchEpsilon; +const js = cc.js; +const Playable = require('./playable'); +const { EventAnimCurve, EventInfo } = require('./animation-curves'); +const WrapModeMask = require('./types').WrapModeMask; +const binarySearch = require('../core/utils/binary-search').binarySearchEpsilon; // The actual animator for Animation Component @@ -43,7 +39,7 @@ function AnimationAnimator (target, animation) { this._anims = new js.array.MutableForwardIterator([]); } js.extend(AnimationAnimator, Playable); -var p = AnimationAnimator.prototype; +let p = AnimationAnimator.prototype; p.playState = function (state, startTime) { if (!state.clip) { @@ -65,10 +61,10 @@ p.playState = function (state, startTime) { }; p.stopStatesExcept = function (state) { - var iterator = this._anims; - var array = iterator.array; + let iterator = this._anims; + let array = iterator.array; for (iterator.i = 0; iterator.i < array.length; ++iterator.i) { - var anim = array[iterator.i]; + let anim = array[iterator.i]; if (anim === state) { continue; } @@ -78,16 +74,16 @@ p.stopStatesExcept = function (state) { }; p.addAnimation = function (anim) { - var index = this._anims.array.indexOf(anim); + let index = this._anims.array.indexOf(anim); if (index === -1) { this._anims.push(anim); } - anim._setListeners(this.animation); + anim._setEventTarget(this.animation); }; p.removeAnimation = function (anim) { - var index = this._anims.array.indexOf(anim); + let index = this._anims.array.indexOf(anim); if (index >= 0) { this._anims.fastRemoveAt(index); @@ -96,17 +92,17 @@ p.removeAnimation = function (anim) { } } else { - cc.errorID(3908); + cc.errorID(3907); } anim.animator = null; }; p.sample = function () { - var iterator = this._anims; - var array = iterator.array; + let iterator = this._anims; + let array = iterator.array; for (iterator.i = 0; iterator.i < array.length; ++iterator.i) { - var anim = array[iterator.i]; + let anim = array[iterator.i]; anim.sample(); } }; @@ -143,9 +139,9 @@ p.setStateTime = function (state, time) { else { time = state; - var array = this._anims.array; - for (var i = 0; i < array.length; ++i) { - var anim = array[i]; + let array = this._anims.array; + for (let i = 0; i < array.length; ++i) { + let anim = array[i]; anim.setTime(time); anim.sample(); } @@ -153,18 +149,18 @@ p.setStateTime = function (state, time) { }; p.onStop = function () { - var iterator = this._anims; - var array = iterator.array; + let iterator = this._anims; + let array = iterator.array; for (iterator.i = 0; iterator.i < array.length; ++iterator.i) { - var anim = array[iterator.i]; + let anim = array[iterator.i]; anim.stop(); } }; p.onPause = function () { - var array = this._anims.array; - for (var i = 0; i < array.length; ++i) { - var anim = array[i]; + let array = this._anims.array; + for (let i = 0; i < array.length; ++i) { + let anim = array[i]; anim.pause(); // need to unbind animator to anim, or it maybe cannot be gc. @@ -173,9 +169,9 @@ p.onPause = function () { }; p.onResume = function () { - var array = this._anims.array; - for (var i = 0; i < array.length; ++i) { - var anim = array[i]; + let array = this._anims.array; + for (let i = 0; i < array.length; ++i) { + let anim = array[i]; // rebind animator to anim anim.animator = this; @@ -191,18 +187,18 @@ p._reloadClip = function (state) { // 这个方法应该是 SampledAnimCurve 才能用 function createBatchedProperty (propPath, firstDotIndex, mainValue, animValue) { mainValue = mainValue.clone(); - var nextValue = mainValue; - var leftIndex = firstDotIndex + 1; - var rightIndex = propPath.indexOf('.', leftIndex); + let nextValue = mainValue; + let leftIndex = firstDotIndex + 1; + let rightIndex = propPath.indexOf('.', leftIndex); // scan property path while (rightIndex !== -1) { - var nextName = propPath.slice(leftIndex, rightIndex); + let nextName = propPath.slice(leftIndex, rightIndex); nextValue = nextValue[nextName]; leftIndex = rightIndex + 1; rightIndex = propPath.indexOf('.', leftIndex); } - var lastPropName = propPath.slice(leftIndex); + let lastPropName = propPath.slice(leftIndex); nextValue[lastPropName] = animValue; return mainValue; @@ -212,19 +208,9 @@ if (CC_TEST) { cc._Test.createBatchedProperty = createBatchedProperty; } -function splitPropPath (propPath) { - var array = propPath.split('.'); - array.shift(); - //array = array.filter(function (item) { return !!item; }); - return array.length > 0 ? array : null; -} - function initClipData (root, state) { - var clip = state.clip; - - var curves = state.curves; - curves.length = 0; + let clip = state.clip; state.duration = clip.duration; state.speed = clip.speed; @@ -238,175 +224,14 @@ function initClipData (root, state) { state.repeatCount = 1; } - // create curves - - function checkMotionPath(motionPath) { - if (!Array.isArray(motionPath)) return false; - - for (let i = 0, l = motionPath.length; i < l; i++) { - var controls = motionPath[i]; - - if (!Array.isArray(controls) || controls.length !== 6) return false; - } - - return true; - } - - function createPropCurve (target, propPath, keyframes) { - var isMotionPathProp = (target instanceof cc.Node) - && (propPath === 'position') - && (keyframes[0] && Array.isArray(keyframes[0].value)); - var motionPaths = []; - - var curve = new DynamicAnimCurve(); - - // 缓存目标对象,所以 Component 必须一开始都创建好并且不能运行时动态替换…… - curve.target = target; - - var propName, propValue; - var dotIndex = propPath.indexOf('.'); - var hasSubProp = dotIndex !== -1; - if (hasSubProp) { - propName = propPath.slice(0, dotIndex); - propValue = target[propName]; - - // if (!(propValue instanceof cc.ValueType)) { - // cc.error('Only support sub animation property which is type cc.ValueType'); - // continue; - // } - } - else { - propName = propPath; - } - - curve.prop = propName; - - curve.subProps = splitPropPath(propPath); - - // for each keyframes - for (let i = 0, l = keyframes.length; i < l; i++) { - var keyframe = keyframes[i]; - var ratio = keyframe.frame / state.duration; - curve.ratios.push(ratio); - - if (isMotionPathProp) { - var motionPath = keyframe.motionPath; - - if (motionPath && !checkMotionPath(motionPath)) { - cc.errorID(3904, target.name, propPath, i); - motionPath = null; - } - - motionPaths.push(motionPath); - } - - var curveValue = keyframe.value; - //if (hasSubProp) { - // curveValue = createBatchedProperty(propPath, dotIndex, propValue, curveValue); - //} - curve.values.push(curveValue); - - var curveTypes = keyframe.curve; - if (curveTypes) { - if (typeof curveTypes === 'string') { - curve.types.push(curveTypes); - continue; - } - else if (Array.isArray(curveTypes)) { - if (curveTypes[0] === curveTypes[1] && - curveTypes[2] === curveTypes[3]) { - curve.types.push(DynamicAnimCurve.Linear); - } - else { - curve.types.push(DynamicAnimCurve.Bezier(curveTypes)); - } - continue; - } - } - curve.types.push(DynamicAnimCurve.Linear); - } - - if (isMotionPathProp) { - sampleMotionPaths(motionPaths, curve, clip.duration, clip.sample); - } - - // if every piece of ratios are the same, we can use the quick function to find frame index. - var ratios = curve.ratios; - var currRatioDif, lastRatioDif; - var canOptimize = true; - var EPSILON = 1e-6; - for (let i = 1, l = ratios.length; i < l; i++) { - currRatioDif = ratios[i] - ratios[i-1]; - if (i === 1) { - lastRatioDif = currRatioDif; - } - else if (Math.abs(currRatioDif - lastRatioDif) > EPSILON) { - canOptimize = false; - break; - } - } - - curve._findFrameIndex = canOptimize ? quickFindIndex : binarySearch; - - return curve; - } - - function createTargetCurves (target, curveData) { - var propsData = curveData.props; - var compsData = curveData.comps; - - if (propsData) { - for (var propPath in propsData) { - var data = propsData[propPath]; - var curve = createPropCurve(target, propPath, data); - - curves.push(curve); - } - } - - if (compsData) { - for (var compName in compsData) { - var comp = target.getComponent(compName); - - if (!comp) { - continue; - } - - var compData = compsData[compName]; - for (var propPath in compData) { - var data = compData[propPath]; - var curve = createPropCurve(comp, propPath, data); - - curves.push(curve); - } - } - } - } - - // property curves - - var curveData = clip.curveData; - var childrenCurveDatas = curveData.paths; - - createTargetCurves(root, curveData); - - for (var namePath in childrenCurveDatas) { - var target = cc.find(namePath, root); - - if (!target) { - continue; - } - - var childCurveDatas = childrenCurveDatas[namePath]; - createTargetCurves(target, childCurveDatas); - } + let curves = state.curves = clip.createCurves(state, root); // events curve - var events = clip.events; + let events = clip.events; if (!CC_EDITOR && events) { - var curve; + let curve; for (let i = 0, l = events.length; i < l; i++) { if (!curve) { @@ -415,11 +240,11 @@ function initClipData (root, state) { curves.push(curve); } - var eventData = events[i]; - var ratio = eventData.frame / state.duration; + let eventData = events[i]; + let ratio = eventData.frame / state.duration; - var eventInfo; - var index = binarySearch(curve.ratios, ratio); + let eventInfo; + let index = binarySearch(curve.ratios, ratio); if (index >= 0) { eventInfo = curve.events[index]; } diff --git a/cocos2d/animation/animation-clip.js b/cocos2d/animation/animation-clip.js index a5c3aad211c..22290e75a91 100644 --- a/cocos2d/animation/animation-clip.js +++ b/cocos2d/animation/animation-clip.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -24,6 +24,9 @@ ****************************************************************************/ const WrapMode = require('./types').WrapMode; +const { DynamicAnimCurve, quickFindIndex } = require('./animation-curves'); +const sampleMotionPaths = require('./motion-path-helper').sampleMotionPaths; +const binarySearch = require('../core/utils/binary-search').binarySearchEpsilon; /** * !#en Class for animation data handling. @@ -38,7 +41,7 @@ var AnimationClip = cc.Class({ properties: { _duration: { default: 0, - type: 'Float', + type: cc.Float, }, /** @@ -151,6 +154,154 @@ var AnimationClip = cc.Class({ return clip; } + }, + + onLoad () { + this._duration = Number.parseFloat(this.duration); + this.speed = Number.parseFloat(this.speed); + this.wrapMode = Number.parseInt(this.wrapMode); + this.frameRate = Number.parseFloat(this.sample); + }, + + createPropCurve (target, propPath, keyframes) { + let motionPaths = []; + let isMotionPathProp = target instanceof cc.Node && propPath === 'position'; + + let curve = new DynamicAnimCurve(); + + // 缓存目标对象,所以 Component 必须一开始都创建好并且不能运行时动态替换…… + curve.target = target; + curve.prop = propPath; + + // for each keyframes + for (let i = 0, l = keyframes.length; i < l; i++) { + let keyframe = keyframes[i]; + let ratio = keyframe.frame / this.duration; + curve.ratios.push(ratio); + + if (isMotionPathProp) { + motionPaths.push(keyframe.motionPath); + } + + let curveValue = keyframe.value; + curve.values.push(curveValue); + + let curveTypes = keyframe.curve; + if (curveTypes) { + if (typeof curveTypes === 'string') { + curve.types.push(curveTypes); + continue; + } + else if (Array.isArray(curveTypes)) { + if (curveTypes[0] === curveTypes[1] && + curveTypes[2] === curveTypes[3]) { + curve.types.push(DynamicAnimCurve.Linear); + } + else { + curve.types.push(DynamicAnimCurve.Bezier(curveTypes)); + } + continue; + } + } + curve.types.push(DynamicAnimCurve.Linear); + } + + if (isMotionPathProp) { + sampleMotionPaths(motionPaths, curve, this.duration, this.sample, target); + } + + // if every piece of ratios are the same, we can use the quick function to find frame index. + let ratios = curve.ratios; + let currRatioDif, lastRatioDif; + let canOptimize = true; + let EPSILON = 1e-6; + for (let i = 1, l = ratios.length; i < l; i++) { + currRatioDif = ratios[i] - ratios[i-1]; + if (i === 1) { + lastRatioDif = currRatioDif; + } + else if (Math.abs(currRatioDif - lastRatioDif) > EPSILON) { + canOptimize = false; + break; + } + } + + curve._findFrameIndex = canOptimize ? quickFindIndex : binarySearch; + + // find the lerp function + let firstValue = curve.values[0]; + if (firstValue !== undefined && firstValue !== null && !curve._lerp) { + if (typeof firstValue === 'number') { + curve._lerp = DynamicAnimCurve.prototype._lerpNumber; + } + else if (firstValue instanceof cc.Quat) { + curve._lerp = DynamicAnimCurve.prototype._lerpQuat; + } + else if (firstValue instanceof cc.Vec2) { + curve._lerp = DynamicAnimCurve.prototype._lerpVector2; + } + else if (firstValue instanceof cc.Vec3) { + curve._lerp = DynamicAnimCurve.prototype._lerpVector3; + } + else if (firstValue.lerp) { + curve._lerp = DynamicAnimCurve.prototype._lerpObject; + } + } + + return curve; + }, + + createTargetCurves (target, curveData, curves) { + let propsData = curveData.props; + let compsData = curveData.comps; + + if (propsData) { + for (let propPath in propsData) { + let data = propsData[propPath]; + let curve = this.createPropCurve(target, propPath, data); + + curves.push(curve); + } + } + + if (compsData) { + for (let compName in compsData) { + let comp = target.getComponent(compName); + + if (!comp) { + continue; + } + + let compData = compsData[compName]; + for (let propPath in compData) { + let data = compData[propPath]; + let curve = this.createPropCurve(comp, propPath, data); + + curves.push(curve); + } + } + } + }, + + createCurves (state, root) { + let curveData = this.curveData; + let childrenCurveDatas = curveData.paths; + let curves = []; + + this.createTargetCurves(root, curveData, curves); + + for (let namePath in childrenCurveDatas) { + let target = cc.find(namePath, root); + + if (!target) { + continue; + } + + let childCurveDatas = childrenCurveDatas[namePath]; + this.createTargetCurves(target, childCurveDatas, curves); + } + + return curves; } }); diff --git a/cocos2d/animation/animation-curves.js b/cocos2d/animation/animation-curves.js index e883e590528..e80e2ae1df4 100644 --- a/cocos2d/animation/animation-curves.js +++ b/cocos2d/animation/animation-curves.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -24,20 +24,20 @@ ****************************************************************************/ -var bezierByTime = require('./bezier').bezierByTime; +const bezierByTime = require('./bezier').bezierByTime; -var binarySearch = require('../core/utils/binary-search').binarySearchEpsilon; -var WrapModeMask = require('./types').WrapModeMask; -var WrappedInfo = require('./types').WrappedInfo; +const binarySearch = require('../core/utils/binary-search').binarySearchEpsilon; +const WrapModeMask = require('./types').WrapModeMask; +const WrappedInfo = require('./types').WrappedInfo; /** * Compute a new ratio by curve type * @param {Number} ratio - The origin ratio - * @param {Array|String} type - If it's Array, then ratio will be computed with bezierByTime. If it's string, then ratio will be computed with cc.Easing function + * @param {Array|String} type - If it's Array, then ratio will be computed with bezierByTime. If it's string, then ratio will be computed with cc.easing function */ function computeRatioByType (ratio, type) { if (typeof type === 'string') { - var func = cc.Easing[type]; + var func = cc.easing[type]; if (func) { ratio = func(ratio); } @@ -86,7 +86,7 @@ function quickFindIndex (ratios, ratio) { if (ratio < start) return 0; var end = ratios[length]; - if (ratio > end) return length; + if (ratio > end) return ~ratios.length; ratio = (ratio - start) / (end - start); @@ -98,6 +98,9 @@ function quickFindIndex (ratios, ratio) { if ((index - floorIndex) < EPSILON) { return floorIndex; } + else if ((floorIndex + 1 - index) < EPSILON) { + return floorIndex + 1; + } return ~(floorIndex + 1); } @@ -112,6 +115,11 @@ var DynamicAnimCurve = cc.Class({ name: 'cc.DynamicAnimCurve', extends: AnimCurve, + ctor () { + // cache last frame index + this._cachedIndex = 0; + }, + properties: { // The object being animated. @@ -140,26 +148,70 @@ var DynamicAnimCurve = cc.Class({ // - [x, x, x, x]: Four control points for bezier // - null: linear types: [], - - // @property {string[]} subProps - The path of sub property being animated. - subProps: null }, _findFrameIndex: binarySearch, + _lerp: undefined, - sample: function (time, ratio, state) { - var values = this.values; - var ratios = this.ratios; - var frameCount = ratios.length; + _lerpNumber (from, to, t) { + return from + (to - from) * t; + }, + + _lerpObject (from, to, t) { + return from.lerp(to, t); + }, + + _lerpQuat: (function () { + let out = cc.quat(); + return function (from, to, t) { + return from.lerp(to, t, out); + }; + })(), + + _lerpVector2: (function () { + let out = cc.v2(); + return function (from, to, t) { + return from.lerp(to, t, out); + }; + })(), + + _lerpVector3: (function () { + let out = cc.v3(); + return function (from, to, t) { + return from.lerp(to, t, out); + }; + })(), + + sample (time, ratio, state) { + let values = this.values; + let ratios = this.ratios; + let frameCount = ratios.length; if (frameCount === 0) { return; } - // evaluate value - var value; - var index = this._findFrameIndex(ratios, ratio); + // only need to refind frame index when ratio is out of range of last from ratio and to ratio. + let shoudRefind = true; + let cachedIndex = this._cachedIndex; + if (cachedIndex < 0) { + cachedIndex = ~cachedIndex; + if (cachedIndex > 0 && cachedIndex < ratios.length) { + let fromRatio = ratios[cachedIndex - 1]; + let toRatio = ratios[cachedIndex]; + if (ratio > fromRatio && ratio < toRatio) { + shoudRefind = false; + } + } + } + if (shoudRefind) { + this._cachedIndex = this._findFrameIndex(ratios, ratio); + } + + // evaluate value + let value; + let index = this._cachedIndex; if (index < 0) { index = ~index; @@ -172,10 +224,7 @@ var DynamicAnimCurve = cc.Class({ else { var fromVal = values[index - 1]; - var isNumber = typeof fromVal === 'number'; - var canLerp = fromVal && fromVal.lerp; - - if (!isNumber && !canLerp) { + if (!this._lerp) { value = fromVal; } else { @@ -191,13 +240,7 @@ var DynamicAnimCurve = cc.Class({ // calculate value var toVal = values[index]; - // lerp - if (isNumber) { - value = fromVal + (toVal - fromVal) * ratioBetweenFrames; - } - else if (canLerp) { - value = fromVal.lerp(toVal, ratioBetweenFrames); - } + value = this._lerp(fromVal, toVal, ratioBetweenFrames); } } } @@ -205,35 +248,6 @@ var DynamicAnimCurve = cc.Class({ value = values[index]; } - var subProps = this.subProps; - if (subProps) { - // create batched value dynamically - var mainProp = this.target[this.prop]; - var subProp = mainProp; - - for (var i = 0; i < subProps.length - 1; i++) { - var subPropName = subProps[i]; - if (subProp) { - subProp = subProp[subPropName]; - } - else { - return; - } - } - - var propName = subProps[subProps.length - 1]; - - if (subProp) { - subProp[propName] = value; - } - else { - return; - } - - value = mainProp; - } - - // apply value this.target[this.prop] = value; } }); @@ -244,7 +258,6 @@ DynamicAnimCurve.Bezier = function (controlPoints) { }; - /** * Event information, * @class EventInfo @@ -438,6 +451,7 @@ var EventAnimCurve = cc.Class({ if (CC_TEST) { cc._Test.DynamicAnimCurve = DynamicAnimCurve; cc._Test.EventAnimCurve = EventAnimCurve; + cc._Test.quickFindIndex = quickFindIndex; } module.exports = { diff --git a/cocos2d/animation/animation-manager.js b/cocos2d/animation/animation-manager.js index 1edd4ec9b84..4b1f3111395 100644 --- a/cocos2d/animation/animation-manager.js +++ b/cocos2d/animation/animation-manager.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -46,11 +46,12 @@ var AnimationManager = cc.Class({ } var events = this._delayEvents; - for (let i = 0, l = events.length; i < l; i++) { + for (let i = 0; i < events.length; i++) { var event = events[i]; event.target[event.func].apply(event.target, event.args); } events.length = 0; + }, destruct: function () {}, diff --git a/cocos2d/animation/animation-state.js b/cocos2d/animation/animation-state.js index 2c2c9135a1a..cbb8c233433 100644 --- a/cocos2d/animation/animation-state.js +++ b/cocos2d/animation/animation-state.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -70,6 +70,7 @@ function AnimationState (clip, name) { /** * @property animator * @type {AnimationAnimator} + * @private */ this.animator = null; @@ -213,7 +214,7 @@ proto.off = function (type, callback, target) { } }; -proto._setListeners = function (target) { +proto._setEventTarget = function (target) { this._target = target; }; diff --git a/cocos2d/animation/bezier.js b/cocos2d/animation/bezier.js index f5dea5176a0..f75cd52be41 100644 --- a/cocos2d/animation/bezier.js +++ b/cocos2d/animation/bezier.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -40,11 +40,8 @@ // return bezier; //})(); function bezier (C1, C2, C3, C4, t) { - var t1 = 1 - t; - return C1 * t1 * t1 * t1 + - C2 * 3 * t1 * t1 * t + - C3 * 3 * t1 * t * t + - C4 * t * t * t; + var t1 = 1 - t; + return t1 * (t1 * (C1 + (C2 * 3 - C1) * t) + C3 * 3 * t * t) + C4 * t * t * t; } //function bezier (c0, c1, c2, c3, t) { // var cy = 3.0 * (c1); @@ -199,15 +196,10 @@ function cardano (curve, x) { function bezierByTime (controlPoints, x) { var percent = cardano(controlPoints, x); // t - var p0y = 0; // a var p1y = controlPoints[1]; // b var p2y = controlPoints[3]; // c - var p3y = 1; // d - var t1 = 1 - percent; - return p0y * t1 * t1 * t1 + - p1y * 3 * percent * t1 * t1 + - p2y * 3 * percent * percent * t1 + - p3y * percent * percent * percent; + // return bezier(0, p1y, p2y, 1, percent); + return ((1 - percent) * (p1y + (p2y - p1y) * percent) * 3 + percent * percent) * percent; } if (CC_TEST) { diff --git a/cocos2d/animation/easing.js b/cocos2d/animation/easing.js index f17aadfde4c..f5f5f0d5b56 100644 --- a/cocos2d/animation/easing.js +++ b/cocos2d/animation/easing.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -23,17 +23,52 @@ THE SOFTWARE. ****************************************************************************/ -var Easing = { +/** + * @module cc + */ + + /** + * !#en + * This class provide easing methods for {{#crossLink "tween"}}{{/crossLink}} class.
+ * Demonstratio: https://easings.net/ + * !#zh + * 缓动函数类,为 {{#crossLink "Tween"}}{{/crossLink}} 提供缓动效果函数。
+ * 函数效果演示: https://easings.net/ + * @class Easing + */ + +var easing = { constant: function () { return 0; }, linear: function (k) { return k; }, // quad - // Easing equation function for a quadratic (t^2) + // easing equation function for a quadratic (t^2) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in with quadratic formula. From slow to fast. + * !#zh 平方曲线缓入函数。运动由慢到快。 + * @method quadIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value + */ quadIn: function (k) { return k * k; }, + /** + * !#en Easing out with quadratic formula. From fast to slow. + * !#zh 平方曲线缓出函数。运动由快到慢。 + * @method quadOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value + */ quadOut: function (k) { return k * ( 2 - k ); }, + /** + * !#en Easing in and out with quadratic formula. From slow to fast, then back to slow. + * !#zh 平方曲线缓入缓出函数。运动由慢到快再到慢。 + * @method quadInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value + */ quadInOut: function (k) { if (( k *= 2 ) < 1) { return 0.5 * k * k; @@ -42,12 +77,33 @@ var Easing = { }, // cubic - // Easing equation function for a cubic (t^3) + // easing equation function for a cubic (t^3) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in with cubic formula. From slow to fast. + * !#zh 立方曲线缓入函数。运动由慢到快。 + * @method cubicIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ cubicIn: function (k) { return k * k * k; }, + /** + * !#en Easing out with cubic formula. From slow to fast. + * !#zh 立方曲线缓出函数。运动由快到慢。 + * @method cubicOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ cubicOut: function (k) { return --k * k * k + 1; }, + /** + * !#en Easing in and out with cubic formula. From slow to fast, then back to slow. + * !#zh 立方曲线缓入缓出函数。运动由慢到快再到慢。 + * @method cubicInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ cubicInOut: function (k) { if (( k *= 2 ) < 1) { return 0.5 * k * k * k; @@ -56,13 +112,34 @@ var Easing = { }, // quart - // Easing equation function for a quartic (t^4) + // easing equation function for a quartic (t^4) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in with quartic formula. From slow to fast. + * !#zh 四次方曲线缓入函数。运动由慢到快。 + * @method quartIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ quartIn: function (k) { return k * k * k * k; }, + /** + * !#en Easing out with quartic formula. From fast to slow. + * !#zh 四次方曲线缓出函数。运动由快到慢。 + * @method quartOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ quartOut: function (k) { return 1 - ( --k * k * k * k ); }, - quartInOut: function (k) { + /** + * !#en Easing in and out with quartic formula. From slow to fast, then back to slow. + * !#zh 四次方曲线缓入缓出函数。运动由慢到快再到慢。 + * @method quartInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ + quartInOut: function (k) { if (( k *= 2 ) < 1) { return 0.5 * k * k * k * k; } @@ -70,12 +147,33 @@ var Easing = { }, // quint - // Easing equation function for a quintic (t^5) + // easing equation function for a quintic (t^5) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in with quintic formula. From slow to fast. + * !#zh 五次方曲线缓入函数。运动由慢到快。 + * @method quintIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ quintIn: function (k) { return k * k * k * k * k; }, + /** + * !#en Easing out with quintic formula. From fast to slow. + * !#zh 五次方曲线缓出函数。运动由快到慢。 + * @method quintOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ quintOut: function (k) { return --k * k * k * k * k + 1; }, + /** + * !#en Easing in and out with quintic formula. From slow to fast, then back to slow. + * !#zh 五次方曲线缓入缓出函数。运动由慢到快再到慢。 + * @method quintInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ quintInOut: function (k) { if (( k *= 2 ) < 1) { return 0.5 * k * k * k * k * k; @@ -84,21 +182,63 @@ var Easing = { }, // sine - // Easing equation function for a sinusoidal (sin(t)) + // easing equation function for a sinusoidal (sin(t)) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in and out with sine formula. From slow to fast. + * !#zh 正弦曲线缓入函数。运动由慢到快。 + * @method sineIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ sineIn: function (k) { return 1 - Math.cos(k * Math.PI / 2); }, + /** + * !#en Easing in and out with sine formula. From fast to slow. + * !#zh 正弦曲线缓出函数。运动由快到慢。 + * @method sineOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ sineOut: function (k) { return Math.sin(k * Math.PI / 2); }, + /** + * !#en Easing in and out with sine formula. From slow to fast, then back to slow. + * !#zh 正弦曲线缓入缓出函数。运动由慢到快再到慢。 + * @method sineInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ sineInOut: function (k) { return 0.5 * ( 1 - Math.cos(Math.PI * k) ); }, // expo - // Easing equation function for an exponential (2^t) + // easing equation function for an exponential (2^t) // param t: Current time (in frames or seconds). // return: The correct value. + /** + * !#en Easing in and out with exponential formula. From slow to fast. + * !#zh 指数曲线缓入函数。运动由慢到快。 + * @method expoIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ expoIn: function (k) { return k === 0 ? 0 : Math.pow(1024, k - 1); }, + /** + * !#en Easing in and out with exponential formula. From fast to slow. + * !#zh 指数曲线缓出函数。运动由快到慢。 + * @method expoOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ expoOut: function (k) { return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); }, + /** + * !#en Easing in and out with exponential formula. From slow to fast. + * !#zh 指数曲线缓入和缓出函数。运动由慢到很快再到慢。 + * @method expoInOut + * @param {Number} t The current time as a percentage of the total time, then back to slow. + * @return {Number} The correct value. + */ expoInOut: function (k) { if (k === 0) { return 0; @@ -113,12 +253,33 @@ var Easing = { }, // circ - // Easing equation function for a circular (sqrt(1-t^2)) + // easing equation function for a circular (sqrt(1-t^2)) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in and out with circular formula. From slow to fast. + * !#zh 循环公式缓入函数。运动由慢到快。 + * @method circIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ circIn: function (k) { return 1 - Math.sqrt(1 - k * k); }, + /** + * !#en Easing in and out with circular formula. From fast to slow. + * !#zh 循环公式缓出函数。运动由快到慢。 + * @method circOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ circOut: function (k) { return Math.sqrt(1 - ( --k * k )); }, + /** + * !#en Easing in and out with circular formula. From slow to fast. + * !#zh 指数曲线缓入缓出函数。运动由慢到很快再到慢。 + * @method circInOut + * @param {Number} t The current time as a percentage of the total time, then back to slow. + * @return {Number} The correct value. + */ circInOut: function (k) { if (( k *= 2 ) < 1) { return -0.5 * ( Math.sqrt(1 - k * k) - 1); @@ -127,11 +288,18 @@ var Easing = { }, // elastic - // Easing equation function for an elastic (exponentially decaying sine wave) + // easing equation function for an elastic (exponentially decaying sine wave) // @param t: Current time (in frames or seconds). // @return: The correct value. // recommand value: elastic (t) + /** + * !#en Easing in action with a spring oscillating effect. + * !#zh 弹簧回震效果的缓入函数。 + * @method elasticIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ elasticIn: function (k) { var s, a = 0.1, p = 0.4; if (k === 0) { @@ -149,6 +317,13 @@ var Easing = { } return -( a * Math.pow(2, 10 * ( k -= 1 )) * Math.sin(( k - s ) * ( 2 * Math.PI ) / p) ); }, + /** + * !#en Easing out action with a spring oscillating effect. + * !#zh 弹簧回震效果的缓出函数。 + * @method elasticOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ elasticOut: function (k) { var s, a = 0.1, p = 0.4; if (k === 0) { @@ -166,6 +341,13 @@ var Easing = { } return ( a * Math.pow(2, -10 * k) * Math.sin(( k - s ) * ( 2 * Math.PI ) / p) + 1 ); }, + /** + * !#en Easing in and out action with a spring oscillating effect. + * !#zh 弹簧回震效果的缓入缓出函数。 + * @method elasticInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ elasticInOut: function (k) { var s, a = 0.1, p = 0.4; if (k === 0) { @@ -189,18 +371,39 @@ var Easing = { }, // back - // Easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) + // easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in action with "back up" behavior. + * !#zh 回退效果的缓入函数。 + * @method backIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ backIn: function (k) { var s = 1.70158; return k * k * ( ( s + 1 ) * k - s ); }, + /** + * !#en Easing out action with "back up" behavior. + * !#zh 回退效果的缓出函数。 + * @method backOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ backOut: function (k) { var s = 1.70158; return --k * k * ( ( s + 1 ) * k + s ) + 1; }, + /** + * !#en Easing in and out action with "back up" behavior. + * !#zh 回退效果的缓入缓出函数。 + * @method backInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ backInOut: function (k) { var s = 1.70158 * 1.525; if (( k *= 2 ) < 1) { @@ -210,10 +413,27 @@ var Easing = { }, // bounce - // Easing equation function for a bounce (exponentially decaying parabolic bounce) + // easing equation function for a bounce (exponentially decaying parabolic bounce) // @param t: Current time (in frames or seconds). // @return: The correct value. + /** + * !#en Easing in action with bouncing effect. + * !#zh 弹跳效果的缓入函数。 + * @method bounceIn + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ + bounceIn: function (k) { + return 1 - easing.bounceOut(1 - k); + }, + /** + * !#en Easing out action with bouncing effect. + * !#zh 弹跳效果的缓出函数。 + * @method bounceOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ bounceOut: function (k) { if (k < ( 1 / 2.75 )) { return 7.5625 * k * k; @@ -228,8 +448,27 @@ var Easing = { return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; } }, + /** + * !#en Easing in and out action with bouncing effect. + * !#zh 弹跳效果的缓入缓出函数。 + * @method bounceInOut + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ + bounceInOut: function (k) { + if (k < 0.5) { + return easing.bounceIn(k * 2) * 0.5; + } + return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5; + }, - // smooth + /** + * !#en Target will run action with smooth effect. + * !#zh 平滑效果函数。 + * @method smooth + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ // t<=0: 0 | 0=1: 1 smooth: function (t) { if (t <= 0) { @@ -241,7 +480,13 @@ var Easing = { return t * t * (3 - 2 * t); }, - // fade + /** + * !#en Target will run action with fade effect. + * !#zh 渐褪效果函数。 + * @method fade + * @param {Number} t The current time as a percentage of the total time. + * @return {Number} The correct value. + */ // t<=0: 0 | 0=1: 1 fade: function (t) { if (t <= 0) { @@ -262,22 +507,32 @@ function _makeOutIn (fnIn, fnOut) { return fnIn(2 * k - 1) / 2 + 0.5; }; } -Easing.quadOutIn = _makeOutIn(Easing.quadIn, Easing.quadOut); -Easing.cubicOutIn = _makeOutIn(Easing.cubicIn, Easing.cubicOut); -Easing.quartOutIn = _makeOutIn(Easing.quartIn, Easing.quartOut); -Easing.quintOutIn = _makeOutIn(Easing.quintIn, Easing.quintOut); -Easing.sineOutIn = _makeOutIn(Easing.sineIn, Easing.sineOut); -Easing.expoOutIn = _makeOutIn(Easing.expoIn, Easing.expoOut); -Easing.circOutIn = _makeOutIn(Easing.circIn, Easing.circOut); -Easing.backOutIn = _makeOutIn(Easing.backIn, Easing.backOut); -Easing.backOutIn = _makeOutIn(Easing.backIn, Easing.backOut); -Easing.bounceIn = function (k) { return 1 - Easing.bounceOut(1 - k); }; -Easing.bounceInOut = function (k) { +easing.quadOutIn = _makeOutIn(easing.quadIn, easing.quadOut); +easing.cubicOutIn = _makeOutIn(easing.cubicIn, easing.cubicOut); +easing.quartOutIn = _makeOutIn(easing.quartIn, easing.quartOut); +easing.quintOutIn = _makeOutIn(easing.quintIn, easing.quintOut); +easing.sineOutIn = _makeOutIn(easing.sineIn, easing.sineOut); +easing.expoOutIn = _makeOutIn(easing.expoIn, easing.expoOut); +easing.circOutIn = _makeOutIn(easing.circIn, easing.circOut); +easing.backOutIn = _makeOutIn(easing.backIn, easing.backOut); +easing.bounceIn = function (k) { return 1 - easing.bounceOut(1 - k); }; +easing.bounceInOut = function (k) { if (k < 0.5) { - return Easing.bounceIn(k * 2) * 0.5; + return easing.bounceIn(k * 2) * 0.5; } - return Easing.bounceOut(k * 2 - 1) * 0.5 + 0.5; + return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5; }; -Easing.bounceOutIn = _makeOutIn(Easing.bounceIn, Easing.bounceOut); +easing.bounceOutIn = _makeOutIn(easing.bounceIn, easing.bounceOut); + +/** + * @module cc + */ + +/** + * !#en This is a Easing instance. + * !#zh 这是一个 Easing 类实例。 + * @property easing + * @type Easing + */ -cc.Easing = module.exports = Easing; +cc.easing = module.exports = easing; diff --git a/cocos2d/animation/index.js b/cocos2d/animation/index.js index 224528caaa8..5379ce3e8d0 100644 --- a/cocos2d/animation/index.js +++ b/cocos2d/animation/index.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, diff --git a/cocos2d/animation/motion-path-helper.js b/cocos2d/animation/motion-path-helper.js index 13deab5f6ae..6f88d8155e5 100644 --- a/cocos2d/animation/motion-path-helper.js +++ b/cocos2d/animation/motion-path-helper.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -216,7 +216,19 @@ Bezier.prototype.getUtoTmapping = function ( u, distance ) { }; -function sampleMotionPaths (motionPaths, data, duration, fps) { +function checkMotionPath(motionPath) { + if (!Array.isArray(motionPath)) return false; + + for (let i = 0, l = motionPath.length; i < l; i++) { + let controls = motionPath[i]; + + if (!Array.isArray(controls) || controls.length !== 6) return false; + } + + return true; +} + +function sampleMotionPaths (motionPaths, data, duration, fps, target) { function createControlPoints(array) { if (array instanceof cc.Vec2) { @@ -241,18 +253,35 @@ function sampleMotionPaths (motionPaths, data, duration, fps) { }; } - var values = data.values; + let values = data.values = data.values.map(function (value) { + if (Array.isArray(value)) { + value = value.length === 2 ? cc.v2(value[0], value[1]) : cc.v3(value[0], value[1], value[2]); + } + return value; + }); if (motionPaths.length === 0 || values.length === 0) { return; } - values = values.map(function (value) { - return v2(value[0], value[1]); - }); + let motionPathValid = false; + for (let i = 0; i < motionPaths.length; i++) { + let motionPath = motionPaths[i]; + if (motionPath && !checkMotionPath(motionPath)) { + cc.errorID(3904, target ? target.name : '', 'position', i); + motionPath = null; + } + if (motionPath && motionPath.length > 0) { + motionPathValid = true; + break; + } + } + + if (!motionPathValid) { + return; + } if (values.length === 1) { - data.values = values; return; } diff --git a/cocos2d/animation/playable.js b/cocos2d/animation/playable.js index ffacf92808a..417bb6693e1 100644 --- a/cocos2d/animation/playable.js +++ b/cocos2d/animation/playable.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, diff --git a/cocos2d/animation/types.js b/cocos2d/animation/types.js index a4866e312d9..29e6139f1ff 100644 --- a/cocos2d/animation/types.js +++ b/cocos2d/animation/types.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, diff --git a/cocos2d/audio/CCAudio.js b/cocos2d/audio/CCAudio.js index 568120454c9..cf3f41fa50e 100644 --- a/cocos2d/audio/CCAudio.js +++ b/cocos2d/audio/CCAudio.js @@ -36,18 +36,14 @@ let touchPlayList = [ let Audio = function (src) { EventTarget.call(this); - + this._shouldRecycleOnEnded = false; this._src = src; this._element = null; this.id = 0; - - this._volume = 1; - this._loop = false; - this._nextTime = 0; // playback position to set - this._state = Audio.State.INITIALZING; this._onended = function () { + this._state = Audio.State.STOPPED; this.emit('ended'); }.bind(this); }; @@ -105,13 +101,14 @@ Audio.State = { } }; - // proto.mount = function (elem) { - // if (CC_DEBUG) { - // cc.warn('Audio.mount(value) is deprecated. Please use Audio._onLoaded().'); - // } - // }; - proto._onLoaded = function () { + this._createElement(); + this._state = Audio.State.INITIALZING; + this.setVolume(1); + this.setLoop(false); + }; + + proto._createElement = function () { let elem = this._src._nativeAsset; if (elem instanceof HTMLAudioElement) { // Reuse dom audio element @@ -123,210 +120,216 @@ Audio.State = { else { this._element = new WebAudioElement(elem, this); } - - this.setVolume(this._volume); - this.setLoop(this._loop); - if (this._nextTime !== 0) { - this.setCurrentTime(this._nextTime); - } - if (this._state === Audio.State.PLAYING) { - this.play(); - } - else { - this._state = Audio.State.INITIALZING; - } }; proto.play = function () { - // marked as playing so it will playOnLoad - this._state = Audio.State.PLAYING; + let self = this; + this._src && this._src._ensureLoaded(function () { + // marked as playing so it will playOnLoad + self._state = Audio.State.PLAYING; + // TODO: move to audio event listeners + self._bindEnded(); + let playPromise = self._element.play(); + // dom audio throws an error if pause audio immediately after playing + if (window.Promise && playPromise instanceof Promise) { + playPromise.catch(function (err) { + // do nothing + }); + } + self._touchToPlay(); + }); + }; - if (!this._element) { - return; + proto._touchToPlay = function () { + if (this._src && this._src.loadMode === LoadMode.DOM_AUDIO && + this._element.paused) { + touchPlayList.push({ instance: this, offset: 0, audio: this._element }); } - this._bindEnded(); - this._element.play(); + if (touchBinded) return; + touchBinded = true; - if (!CC_QQPLAY && !CC_WECHATGAME) { - if (this._src && this._src.loadMode === LoadMode.DOM_AUDIO && - this._element.paused) { - touchPlayList.push({ instance: this, offset: 0, audio: this._element }); + let touchEventName = ('ontouchend' in window) ? 'touchend' : 'mousedown'; + // Listen to the touchstart body event and play the audio when necessary. + cc.game.canvas.addEventListener(touchEventName, function () { + let item; + while (item = touchPlayList.pop()) { + item.audio.play(item.offset); } - - if (touchBinded) return; - touchBinded = true; - - // Listen to the touchstart body event and play the audio when necessary. - cc.game.canvas.addEventListener('touchstart', function () { - let item; - while (item = touchPlayList.pop()) { - item.audio.play(item.offset); - } - }); - } + }); }; proto.destroy = function () { - if (CC_WECHATGAME) { - this._element && this._element.destroy(); - } this._element = null; }; proto.pause = function () { - if (!this._element || this._state !== Audio.State.PLAYING) return; - this._unbindEnded(); - this._element.pause(); - this._state = Audio.State.PAUSED; + if (this.getState() !== Audio.State.PLAYING) return; + let self = this; + this._src && this._src._ensureLoaded(function () { + // pause operation may fire 'ended' event + self._unbindEnded(); + self._element.pause(); + self._state = Audio.State.PAUSED; + }); }; proto.resume = function () { - if (!this._element || this._state !== Audio.State.PAUSED) return; - this._bindEnded(); - this._element.play(); - this._state = Audio.State.PLAYING; + if (this.getState() !== Audio.State.PAUSED) return; + let self = this; + this._src && this._src._ensureLoaded(function () { + self._bindEnded(); + self._element.play(); + self._state = Audio.State.PLAYING; + }); }; proto.stop = function () { - if (!this._element) return; - this._element.pause(); - try { - this._element.currentTime = 0; - } catch (error) {} - // remove touchPlayList - for (let i = 0; i < touchPlayList.length; i++) { - if (touchPlayList[i].instance === this) { - touchPlayList.splice(i, 1); - break; + let self = this; + this._src && this._src._ensureLoaded(function () { + self._element.pause(); + self._element.currentTime = 0; + // remove touchPlayList + for (let i = 0; i < touchPlayList.length; i++) { + if (touchPlayList[i].instance === self) { + touchPlayList.splice(i, 1); + break; + } } - } - this._unbindEnded(); - this.emit('stop'); - this._state = Audio.State.STOPPED; + self._unbindEnded(); + self.emit('stop'); + self._state = Audio.State.STOPPED; + }); }; proto.setLoop = function (loop) { - this._loop = loop; - if (this._element) { - this._element.loop = loop; - } + let self = this; + this._src && this._src._ensureLoaded(function () { + self._element.loop = loop; + }); }; proto.getLoop = function () { - return this._loop; + return this._element ? this._element.loop : false; }; proto.setVolume = function (num) { - this._volume = num; - if (this._element) { - this._element.volume = num; - } + let self = this; + this._src && this._src._ensureLoaded(function () { + self._element.volume = num; + }); }; proto.getVolume = function () { - return this._volume; + return this._element ? this._element.volume : 1; }; proto.setCurrentTime = function (num) { - if (this._element) { - this._nextTime = 0; - } - else { - this._nextTime = num; - return; - } - - this._unbindEnded(); - if (!(CC_QQPLAY || CC_WECHATGAME)) { - this._bindEnded(function () { - this._bindEnded(); - }.bind(this)); - } - try { - this._element.currentTime = num; - } - catch (err) { - let _element = this._element; - if (_element.addEventListener) { - let func = function () { - _element.removeEventListener('loadedmetadata', func); - _element.currentTime = num; - }; - _element.addEventListener('loadedmetadata', func); - } - } + let self = this; + this._src && this._src._ensureLoaded(function () { + // setCurrentTime would fire 'ended' event + // so we need to change the callback to rebind ended callback after setCurrentTime + self._unbindEnded(); + self._bindEnded(function () { + self._bindEnded(); + }); + self._element.currentTime = num; + }); }; + proto.getCurrentTime = function () { return this._element ? this._element.currentTime : 0; }; proto.getDuration = function () { - return this._element ? this._element.duration : 0; + return this._src ? this._src.duration : 0; }; - proto.getState = function () { - if (!CC_WECHATGAME) { - let elem = this._element; - if (elem && Audio.State.PLAYING === this._state && elem.paused) { - this._state = Audio.State.PAUSED; - } + proto.getState = function (forceUpdating = true) { + // HACK: in some browser, audio may not fire 'ended' event + // so we need to force updating the Audio state + if (forceUpdating) { + this._forceUpdatingState(); } return this._state; }; - proto.__defineGetter__('src', function () { - return this._src; - }); - proto.__defineSetter__('src', function (clip) { - this._unbindEnded(); - if (clip) { - this._src = clip; - if (clip.loaded) { - this._onLoaded(); + proto._forceUpdatingState = function () { + let elem = this._element; + if (elem) { + if (Audio.State.PLAYING === this._state && elem.paused) { + this._state = Audio.State.STOPPED; } - else { - let self = this; - clip.once('load', function () { - if (clip === self._src) { - self._onLoaded(); - } - }); - cc.loader.load({ - url: clip.nativeUrl, - // For audio, we should skip loader otherwise it will load a new audioClip. - skips: ['Loader'], - }, - function (err, audioNativeAsset) { - if (err) { - cc.error(err); - return; - } - if (!clip.loaded) { - clip._nativeAsset = audioNativeAsset; - } - }); + else if (Audio.State.STOPPED === this._state && !elem.paused) { + this._state = Audio.State.PLAYING; } } - else { - this._src = null; - if (this._element instanceof HTMLAudioElement) { - this._element.src = ''; + }; + + Object.defineProperty(proto, 'src', { + get: function () { + return this._src; + }, + set: function (clip) { + this._unbindEnded(); + if (clip) { + if (clip !== this._src) { + this._src = clip; + if (!clip.loaded) { + let self = this; + // need to call clip._ensureLoaded mannually to start loading + clip.once('load', function () { + // In case set a new src when the old one hasn't finished loading + if (clip === self._src) { + self._onLoaded(); + } + }); + } + else { + this._onLoaded(); + } + } } else { - this._element = null; + this._src = null; + if (this._element instanceof WebAudioElement) { + this._element = null; + } + else if (this._element) { + this._element.src = ''; + } + this._state = Audio.State.INITIALZING; } - this._state = Audio.State.INITIALZING; - } - return clip; + return clip; + }, + enumerable: true, + configurable: true }); - proto.__defineGetter__('paused', function () { - return this._element ? this._element.paused : true; + Object.defineProperty(proto, 'paused', { + get: function () { + return this._element ? this._element.paused : true; + }, + enumerable: true, + configurable: true }); // setFinishCallback })(Audio.prototype); + +// TIME_CONSTANT is used as an argument of setTargetAtTime interface +// TIME_CONSTANT need to be a positive number on Edge and Baidu browser +// TIME_CONSTANT need to be 0 by default, or may fail to set volume at the very beginning of playing audio +let TIME_CONSTANT; +if (cc.sys.browserType === cc.sys.BROWSER_TYPE_EDGE || + cc.sys.browserType === cc.sys.BROWSER_TYPE_BAIDU || + cc.sys.browserType === cc.sys.BROWSER_TYPE_UC) { + TIME_CONSTANT = 0.01; +} +else { + TIME_CONSTANT = 0; +} + // Encapsulated WebAudio interface let WebAudioElement = function (buffer, audio) { this._audio = audio; @@ -334,15 +337,9 @@ let WebAudioElement = function (buffer, audio) { this._buffer = buffer; this._gainObj = this._context['createGain'](); - this._volume = 1; - // https://www.chromestatus.com/features/5287995770929152 - if (this._gainObj['gain'].setTargetAtTime) { - this._gainObj['gain'].setTargetAtTime(this._volume, this._context.currentTime, 0.01); - } else { - this._gainObj['gain'].value = 1; - } - this._gainObj['connect'](this._context['destination']); + this.volume = 1; + this._gainObj['connect'](this._context['destination']); this._loop = false; // The time stamp on the audio time axis when the recording begins to play. this._startTime = -1; @@ -351,7 +348,7 @@ let WebAudioElement = function (buffer, audio) { // Record the time has been played this.playedLength = 0; - this._currextTimer = null; + this._currentTimer = null; this._endCallback = function () { if (this.onended) { @@ -394,7 +391,7 @@ let WebAudioElement = function (buffer, audio) { endTime = duration - offset; if (audio.start) audio.start(0, startTime, endTime); - else if (audio["notoGrainOn"]) + else if (audio["noteGrainOn"]) audio["noteGrainOn"](0, startTime, endTime); else audio["noteOn"](0, startTime, endTime); @@ -408,9 +405,9 @@ let WebAudioElement = function (buffer, audio) { // There may be a need to touch events before you can actually start playing audio if ((!audio.context.state || audio.context.state === "suspended") && this._context.currentTime === 0) { let self = this; - clearTimeout(this._currextTimer); - this._currextTimer = setTimeout(function () { - if (!(CC_QQPLAY || CC_WECHATGAME) && self._context.currentTime === 0) { + clearTimeout(this._currentTimer); + this._currentTimer = setTimeout(function () { + if (self._context.currentTime === 0) { touchPlayList.push({ instance: self._audio, offset: offset, @@ -419,10 +416,22 @@ let WebAudioElement = function (buffer, audio) { } }, 10); } + + let sys = cc.sys; + if (sys.os === sys.OS_IOS && sys.isBrowser && sys.isMobile) { + // Audio context is suspended when you unplug the earphones, + // and is interrupted when the app enters background. + // Both make the audioBufferSource unplayable. + if ((audio.context.state === "suspended" && this._context.currentTime !== 0) + || audio.context.state === 'interrupted') { + // reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/resume + audio.context.resume(); + } + } }; proto.pause = function () { - clearTimeout(this._currextTimer); + clearTimeout(this._currentTimer); if (this.paused) return; // Record the time the current has been played this.playedLength = this._context.currentTime - this._startTime; @@ -435,71 +444,101 @@ let WebAudioElement = function (buffer, audio) { audio.stop(0); }; - proto.__defineGetter__('paused', function () { - // If the current audio is a loop, paused is false - if (this._currentSource && this._currentSource.loop) - return false; - - // startTime default is -1 - if (this._startTime === -1) - return true; - - // Current time - Start playing time > Audio duration - return this._context.currentTime - this._startTime > this._buffer.duration; + Object.defineProperty(proto, 'paused', { + get: function () { + // If the current audio is a loop, paused is false + if (this._currentSource && this._currentSource.loop) + return false; + + // startTime default is -1 + if (this._startTime === -1) + return true; + + // Current time - Start playing time > Audio duration + return this._context.currentTime - this._startTime > this._buffer.duration; + }, + enumerable: true, + configurable: true }); - proto.__defineGetter__('loop', function () { return this._loop; }); - proto.__defineSetter__('loop', function (bool) { - if (this._currentSource) - this._currentSource.loop = bool; - - return this._loop = bool; + Object.defineProperty(proto, 'loop', { + get: function () { + return this._loop; + }, + set: function (bool) { + if (this._currentSource) + this._currentSource.loop = bool; + + return this._loop = bool; + }, + enumerable: true, + configurable: true }); - proto.__defineGetter__('volume', function () { - return this._volume; - }); - proto.__defineSetter__('volume', function (num) { - this._volume = num; - if (this._gainObj['gain'].setTargetAtTime) { - this._gainObj['gain'].setTargetAtTime(this._volume, this._context.currentTime, 0.01); - } else { - this._volume['gain'].value = num; - } - if (sys.os === sys.OS_IOS && !this.paused && this._currentSource) { - // IOS must be stop webAudio - this._currentSource.onended = null; - this.pause(); - this.play(); - } - return num; + Object.defineProperty(proto, 'volume', { + get: function () { + return this._volume; + }, + set: function (num) { + this._volume = num; + // https://www.chromestatus.com/features/5287995770929152 + if (this._gainObj.gain.setTargetAtTime) { + try { + this._gainObj.gain.setTargetAtTime(num, this._context.currentTime, TIME_CONSTANT); + } + catch (e) { + // Some other unknown browsers may crash if TIME_CONSTANT is 0 + this._gainObj.gain.setTargetAtTime(num, this._context.currentTime, 0.01); + } + } + else { + this._gainObj.gain.value = num; + } + + if (sys.os === sys.OS_IOS && !this.paused && this._currentSource) { + // IOS must be stop webAudio + this._currentSource.onended = null; + this.pause(); + this.play(); + } + }, + enumerable: true, + configurable: true }); - proto.__defineGetter__('currentTime', function () { - if (this.paused) { + Object.defineProperty(proto, 'currentTime', { + get: function () { + if (this.paused) { + return this.playedLength; + } + // Record the time the current has been played + this.playedLength = this._context.currentTime - this._startTime; + // If more than the duration of the audio, Need to take the remainder + this.playedLength %= this._buffer.duration; return this.playedLength; - } - // Record the time the current has been played - this.playedLength = this._context.currentTime - this._startTime; - // If more than the duration of the audio, Need to take the remainder - this.playedLength %= this._buffer.duration; - return this.playedLength; - }); - proto.__defineSetter__('currentTime', function (num) { - if (!this.paused) { - this.pause(); - this.playedLength = num; - this.play(); - } else { - this.playedLength = num; - } - return num; + }, + set: function (num) { + if (!this.paused) { + this.pause(); + this.playedLength = num; + this.play(); + } else { + this.playedLength = num; + } + return num; + }, + enumerable: true, + configurable: true }); - proto.__defineGetter__('duration', function () { - return this._buffer.duration; + Object.defineProperty(proto, 'duration', { + get: function () { + return this._buffer.duration; + }, + enumerable: true, + configurable: true }); })(WebAudioElement.prototype); -module.exports = cc.Audio = Audio; +module.exports = cc._Audio = Audio; diff --git a/cocos2d/audio/CCAudioEngine.js b/cocos2d/audio/CCAudioEngine.js index f59bf0ee656..9f0b3a3362e 100644 --- a/cocos2d/audio/CCAudioEngine.js +++ b/cocos2d/audio/CCAudioEngine.js @@ -35,16 +35,24 @@ let _url2id = {}; let _audioPool = []; let recycleAudio = function (audio) { - audio._finishCallback = null; - if (_audioPool.length < 32) { - audio.off('ended'); - audio.off('stop'); - audio.src = null; - _audioPool.push(audio); + // In case repeatly recycle audio when users call audio.stop when audio finish playing + if (!audio._shouldRecycleOnEnded) { + return; } - else { - audio.destroy(); + audio._finishCallback = null; + audio.off('ended'); + audio.off('stop'); + audio.src = null; + // In case repeatly recycle audio + if (!_audioPool.includes(audio)) { + if (_audioPool.length < 32) { + _audioPool.push(audio); + } + else { + audio.destroy(); + } } + audio._shouldRecycleOnEnded = false; }; let getAudioFromPath = function (path) { @@ -75,7 +83,9 @@ let getAudioFromPath = function (path) { if (this._finishCallback) { this._finishCallback(); } - callback.call(this); + if(!this.getLoop()){ + callback.call(this); + } }, audio); audio.on('stop', callback, audio); @@ -90,16 +100,26 @@ let getAudioFromId = function (id) { return _id2audio[id]; }; +let handleVolume = function (volume) { + if (volume === undefined) { + // set default volume as 1 + volume = 1; + } + else if (typeof volume === 'string') { + volume = Number.parseFloat(volume); + } + return volume; +}; + /** - * !#en cc.audioEngine is the singleton object, it provide simple audio APIs. + * !#en `cc.audioEngine` is the singleton object, it provide simple audio APIs. * !#zh * cc.audioengine是单例对象。
* 主要用来播放音频,播放的时候会返回一个 audioID,之后都可以通过这个 audioID 来操作这个音频对象。
- * 不使用的时候,请使用 cc.audioEngine.uncache(filePath); 进行资源释放
+ * 不使用的时候,请使用 `cc.audioEngine.uncache(filePath);` 进行资源释放
* 注意:
* 在 Android 系统浏览器上,不同浏览器,不同版本的效果不尽相同。
- * 比如说:大多数浏览器都需要用户物理交互才可以开始播放音效,有一些不支持 WebAudio,
- * 有一些不支持多音轨播放。总之如果对音乐依赖比较强,请做尽可能多的测试。 + * 比如说:大多数浏览器都需要用户物理交互才可以开始播放音效,有一些不支持 WebAudio,有一些不支持多音轨播放。总之如果对音乐依赖比较强,请做尽可能多的测试。 * @class audioEngine * @static */ @@ -107,7 +127,6 @@ var audioEngine = { AudioState: Audio.State, - _maxWebAudioSize: 2097152, // 2048kb * 1024 _maxAudioInstance: 24, _id2audio: _id2audio, @@ -121,41 +140,23 @@ var audioEngine = { * @param {Number} volume - Volume size. * @return {Number} audioId * @example - * cc.loader.loadRes(url, cc.AudioClip, function (err, clip) { + * cc.resources.load(path, cc.AudioClip, null, function (err, clip) { * var audioID = cc.audioEngine.play(clip, false, 0.5); * }); */ - play: function (clip, loop, volume/*, profile*/) { - var path = clip; - var audio; - if (typeof clip === 'string') { - // backward compatibility since 1.10 - cc.warnID(8401, 'cc.audioEngine', 'cc.AudioClip', 'AudioClip', 'cc.AudioClip', 'audio'); - path = clip; - // load clip - audio = getAudioFromPath(path); - AudioClip._loadByUrl(path, function (err, clip) { - if (clip) { - audio.src = clip; - } - }); - } - else { - if (!clip) { - return; - } - path = clip.nativeUrl; - audio = getAudioFromPath(path); - audio.src = clip; + play: function (clip, loop, volume) { + if (!(clip instanceof AudioClip)) { + return cc.error('Wrong type of AudioClip.'); } - + let path = clip.nativeUrl; + let audio = getAudioFromPath(path); + audio.src = clip; + clip._ensureLoaded(); + audio._shouldRecycleOnEnded = true; audio.setLoop(loop || false); - if (typeof volume !== 'number') { - volume = 1; - } + volume = handleVolume(volume); audio.setVolume(volume); audio.play(); - return audio.id; }, @@ -414,16 +415,20 @@ var audioEngine = { * @param {Number} num - a number of instances to be created from within an audio * @example * cc.audioEngine.setMaxAudioInstance(20); + * @deprecated since v2.4.0 */ setMaxAudioInstance: function (num) { - this._maxAudioInstance = num; + if (CC_DEBUG) { + cc.warn('Since v2.4.0, maxAudioInstance has become a read only property.\n' + + 'audioEngine.setMaxAudioInstance() method will be removed in the future'); + } }, /** * !#en Getting audio can produce several examples. * !#zh 获取一个音频可以设置几个实例 * @method getMaxAudioInstance - * @return {Number} a - number of instances to be created from within an audio + * @return {Number} max number of instances to be created from within an audio * @example * cc.audioEngine.getMaxAudioInstance(); */ @@ -489,49 +494,6 @@ var audioEngine = { _url2id = {}; }, - /** - * !#en Gets an audio profile by name. - * - * @param profileName A name of audio profile. - * @return The audio profile. - */ - getProfile: function (profileName) {}, - - /** - * !#en Preload audio file. - * !#zh 预加载一个音频 - * @method preload - * @param {String} filePath - The file path of an audio. - * @param {Function} [callback] - The callback of an audio. - * @example - * cc.audioEngine.preload(path); - * @deprecated `cc.audioEngine.preload` is deprecated, use `cc.loader.loadRes(url, cc.AudioClip)` instead please. - */ - preload: function (filePath, callback) { - if (CC_DEBUG) { - cc.warn('`cc.audioEngine.preload` is deprecated, use `cc.loader.loadRes(url, cc.AudioClip)` instead please.'); - } - - cc.loader.load(filePath, callback && function (error) { - if (!error) { - callback(); - } - }); - }, - - /** - * !#en Set a size, the unit is KB. Over this size is directly resolved into DOM nodes. - * !#zh 设置一个以 KB 为单位的尺寸,大于这个尺寸的音频在加载的时候会强制使用 dom 方式加载 - * @method setMaxWebAudioSize - * @param {Number} kb - The file path of an audio. - * @example - * cc.audioEngine.setMaxWebAudioSize(300); - */ - // Because webAudio takes up too much memory,So allow users to manually choose - setMaxWebAudioSize: function (kb) { - this._maxWebAudioSize = kb * 1024; - }, - _breakCache: null, _break: function () { this._breakCache = []; @@ -579,7 +541,7 @@ var audioEngine = { * @param {Boolean} loop - Whether the music loop or not. * @return {Number} audioId * @example - * cc.loader.loadRes(url, cc.AudioClip, function (err, clip) { + * cc.resources.load(path, cc.AudioClip, null, function (err, clip) { * var audioID = cc.audioEngine.playMusic(clip, false); * }); */ @@ -647,6 +609,7 @@ var audioEngine = { * cc.audioEngine.setMusicVolume(0.5); */ setMusicVolume: function (volume) { + volume = handleVolume(volume); var music = this._music; music.volume = volume; this.setVolume(music.id, music.volume); @@ -673,7 +636,7 @@ var audioEngine = { * @param {Boolean} loop - Whether the music loop or not. * @return {Number} audioId * @example - * cc.loader.loadRes(url, cc.AudioClip, function (err, clip) { + * cc.resources.load(path, cc.AudioClip, null, function (err, clip) { * var audioID = cc.audioEngine.playEffect(clip, false); * }); */ @@ -690,10 +653,12 @@ var audioEngine = { * cc.audioEngine.setEffectsVolume(0.5); */ setEffectsVolume: function (volume) { + volume = handleVolume(volume); var musicId = this._music.id; this._effect.volume = volume; for (var id in _id2audio) { - if (id === musicId) continue; + var audio = _id2audio[id]; + if (!audio || audio.id === musicId) continue; audioEngine.setVolume(id, volume); } }, @@ -735,8 +700,8 @@ var audioEngine = { effect.pauseCache.length = 0; for (var id in _id2audio) { - if (id === musicId) continue; var audio = _id2audio[id]; + if (!audio || audio.id === musicId) continue; var state = audio.getState(); if (state === this.AudioState.PLAYING) { effect.pauseCache.push(id); @@ -796,8 +761,8 @@ var audioEngine = { stopAllEffects: function () { var musicId = this._music.id; for (var id in _id2audio) { - if (id === musicId) continue; var audio = _id2audio[id]; + if (!audio || audio.id === musicId) continue; var state = audio.getState(); if (state === audioEngine.AudioState.PLAYING) { audio.stop(); @@ -806,4 +771,4 @@ var audioEngine = { } }; -module.exports = cc.audioEngine = audioEngine; \ No newline at end of file +module.exports = cc.audioEngine = audioEngine; diff --git a/cocos2d/core/3d/CCLightComponent.js b/cocos2d/core/3d/CCLightComponent.js new file mode 100644 index 00000000000..467e4d4b77d --- /dev/null +++ b/cocos2d/core/3d/CCLightComponent.js @@ -0,0 +1,446 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import enums from '../../renderer/enums'; +import Color from '../value-types/color'; +import { toRadian } from '../value-types'; + +let RendererLight = null; +if (CC_JSB && CC_NATIVERENDERER) { + // @ts-ignore + RendererLight = window.renderer.Light; +} else { + // @ts-ignore + RendererLight = require('../../renderer/scene/light'); +} + +import renderer from '../renderer/index'; +import Enum from '../platform/CCEnum'; +import CCComponent from '../components/CCComponent'; +import { ccclass, menu, inspector, property, executeInEditMode } from '../platform/CCClassDecorator'; + +/** + * !#en The light source type + * + * !#zh 光源类型 + * @static + * @enum Light.Type + */ +const LightType = Enum({ + /** + * !#en The direction of light + * + * !#zh 平行光 + * @property {Number} DIRECTIONAL + * @readonly + */ + DIRECTIONAL: 0, + /** + * !#en The point of light + * + * !#zh 点光源 + * @property {Number} POINT + * @readonly + */ + POINT: 1, + /** + * !#en The spot of light + * + * !#zh 聚光灯 + * @property {Number} SPOT + * @readonly + */ + SPOT: 2, + + /** + * !#en The ambient light + * !#zh 环境光 + * @property {Number} AMBIENT + * @readonly + */ + AMBIENT: 3 +}); + +/** + * !#en The shadow type + * + * !#zh 阴影类型 + * @static + * @enum Light.ShadowType + */ +const LightShadowType = Enum({ + /** + * !#en No shadows + * + * !#zh 阴影关闭 + * @property NONE + * @readonly + * @type {Number} + */ + NONE: 0, + /** + * !#en Hard shadows + * + * !#zh 阴硬影 + * @property HARD + * @readonly + * @type {Number} + */ + HARD: 2, + /** + * !#en Soft PCF 3x3 shadows + * + * !#zh PCF 3x3 软阴影 + * @property SOFT_PCF3X3 + * @readonly + * @type {Number} + */ + SOFT_PCF3X3: 3, + /** + * !#en Soft PCF 5x5 shadows + * + * !#zh PCF 5x5 软阴影 + * @property SOFT_PCF5X5 + * @readonly + * @type {Number} + */ + SOFT_PCF5X5: 4, +}); + +/** + * !#en The Light Component + * + * !#zh 光源组件 + * @class Light + * @extends Component + */ +@ccclass('cc.Light') +@menu('i18n:MAIN_MENU.component.renderers/Light') +@executeInEditMode +@inspector('packages://inspector/inspectors/comps/light.js') +export default class Light extends CCComponent { + @property + _type = LightType.DIRECTIONAL; + + @property + _color = Color.WHITE; + + @property + _intensity = 1; + + @property + _range = 1000; + + @property + _spotAngle = 60; + + @property + _spotExp = 1; + + @property + _shadowType = LightShadowType.NONE; + + @property + _shadowResolution = 1024; + + @property + _shadowDarkness = 0.5; + + @property + _shadowMinDepth = 1; + + @property + _shadowMaxDepth = 4096; + + @property + _shadowFrustumSize = 1024; + + @property + _shadowBias = 0.0005; + + /** + * !#en The light source type,currently we have directional, point, spot three type. + * !#zh 光源类型,目前有 平行光,聚光灯,点光源 三种类型 + * @type {LightType} + */ + @property({ + type: LightType + }) + get type() { + return this._type; + } + + set type(val) { + this._type = val; + + let type = enums.LIGHT_DIRECTIONAL; + if (val === LightType.POINT) { + type = enums.LIGHT_POINT; + } else if (val === LightType.SPOT) { + type = enums.LIGHT_SPOT; + } + else if (val === LightType.AMBIENT) { + type = enums.LIGHT_AMBIENT; + } + this._light.setType(type); + } + + /** + * !#en The light source color + * !#zh 光源颜色 + * @type {Color} + */ + @property + get color() { + return this._color; + } + + set color(val) { + if (!this._color.equals(val)) { + this._color.set(val); + } + this._light.setColor(val.r / 255, val.g / 255, val.b / 255); + } + + /** + * !#en The light source intensity + * + * !#zh 光源强度 + * @type {Number} + */ + @property + get intensity() { + return this._intensity; + } + + set intensity(val) { + this._intensity = val; + this._light.setIntensity(val); + } + + /** + * !#en The light range, used for spot and point light + * + * !#zh 针对聚光灯和点光源设置光源范围 + * @type {Number} + */ + @property + get range() { + return this._range; + } + + set range(val) { + this._range = val; + this._light.setRange(val); + } + + /** + * !#en The spot light cone angle + * + * !#zh 聚光灯锥角 + * @type {Number} + */ + @property + get spotAngle() { + return this._spotAngle; + } + + set spotAngle(val) { + this._spotAngle = val; + this._light.setSpotAngle(toRadian(val)); + } + + /** + * !#en The spot light exponential + * + * !#zh 聚光灯指数 + * @type {Number} + */ + @property + get spotExp() { + return this._spotExp; + } + + set spotExp(val) { + this._spotExp = val; + this._light.setSpotExp(val); + } + + /** + * !#en The shadow type + * + * !#zh 阴影类型 + * @type {Number} shadowType + */ + @property({ + type: LightShadowType + }) + get shadowType() { + return this._shadowType; + } + + set shadowType(val) { + this._shadowType = val; + this._light.setShadowType(val); + } + + /** + * !#en The shadow resolution + * + * !#zh 阴影分辨率 + * + * @type {Number} + */ + @property + get shadowResolution() { + return this._shadowResolution; + } + + set shadowResolution(val) { + this._shadowResolution = val; + this._light.setShadowResolution(val); + } + + /** + * !#en The shadow darkness + * + * !#zh 阴影灰度值 + * + * @type {Number} + */ + @property + get shadowDarkness() { + return this._shadowDarkness; + } + + set shadowDarkness(val) { + this._shadowDarkness = val; + this._light.setShadowDarkness(val); + } + + /** + * !#en The shadow min depth + * + * !#zh 阴影最小深度 + * + * @type {Number} + */ + @property + get shadowMinDepth() { + return this._shadowMinDepth; + } + + set shadowMinDepth(val) { + this._shadowMinDepth = val; + this._light.setShadowMinDepth(val); + } + + /** + * !#en The shadow max depth + * + * !#zh 阴影最大深度 + * + * @type {Number} + */ + @property + get shadowMaxDepth() { + return this._shadowMaxDepth; + } + + set shadowMaxDepth(val) { + this._shadowMaxDepth = val; + this._light.setShadowMaxDepth(val); + } + + /** + * !#en The shadow frustum size + * + * !#zh 阴影截锥体大小 + * + * @type {Number} + */ + @property + get shadowFrustumSize() { + return this._shadowFrustumSize; + } + + set shadowFrustumSize(val) { + this._shadowFrustumSize = val; + this._light.setShadowFrustumSize(val); + } + + // /** + // * !#en The shadow bias + // * + // * !#zh 阴影偏移量 + // * + // * @type {Number} + // */ + // @property + // get shadowBias() { + // return this._shadowBias; + // } + + // set shadowBias(val) { + // this._shadowBias = val; + // this._light.setShadowBias(val); + // } + + static Type = LightType; + + static ShadowType = LightShadowType; + + constructor() { + super(); + + this._light = new RendererLight(); + } + + onLoad() { + this._light.setNode(this.node); + this.type = this._type; + this.color = this._color; + this.intensity = this._intensity; + this.range = this._range; + this.spotAngle = this._spotAngle; + this.spotExp = this._spotExp; + this.shadowType = this._shadowType; + this.shadowResolution = this._shadowResolution; + this.shadowDarkness = this._shadowDarkness; + this.shadowMaxDepth = this._shadowMaxDepth; + this.shadowFrustumSize = this._shadowFrustumSize; + this.shadowBias = this._shadowBias; + } + + onEnable() { + renderer.scene.addLight(this._light); + } + + onDisable() { + renderer.scene.removeLight(this._light); + } +} + +cc.Light = Light; diff --git a/cocos2d/core/3d/CCModel.js b/cocos2d/core/3d/CCModel.js index da6150d89d0..2f3580b91c0 100644 --- a/cocos2d/core/3d/CCModel.js +++ b/cocos2d/core/3d/CCModel.js @@ -23,425 +23,68 @@ THE SOFTWARE. ****************************************************************************/ -const AnimationClip = require('../../animation/animation-clip'); - -const renderEngine = require('../renderer/render-engine'); -const renderer = require('../renderer'); -const gfx = renderEngine.gfx; -const vec3 = cc.vmath.vec3; - -const _type2size = { - SCALAR: 1, - VEC2: 2, - VEC3: 3, - VEC4: 4, - MAT2: 4, - MAT3: 9, - MAT4: 16, -}; - -const _compType2Array = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5124: Int32Array, - 5125: Uint32Array, - 5126: Float32Array, -}; - -function createArray(gltf, bin, accessorID) { - let acc = gltf.accessors[accessorID]; - let bufView = gltf.bufferViews[acc.bufferView]; - - let num = _type2size[acc.type]; - let typedArray = _compType2Array[acc.componentType]; - let result = new typedArray(bin, bufView.byteOffset + acc.byteOffset, acc.count * num); - - return result; -} - -var Model = cc.Class({ +let Model = cc.Class({ name: 'cc.Model', extends: cc.Asset, - ctor() { - this._bin = null; + ctor () { + this._rootNode = null; + this.loaded = false; }, properties: { - _nativeAsset: { - get() { - return this._bin; - }, - set(bin) { - this._bin = bin.buffer; - this._initNodes(); - }, - override: true + _nodes: { + default: [] }, - _gltf: { - default: {} - } - }, + _precomputeJointMatrix: false, - _initNodes() { - let nodes = this._gltf.nodes; - for (let i = 0; i < nodes.length; i++) { - let node = nodes[i]; - - node.path = node.parent ? node.parent.path + '/' + node.name : ''; - - let children = node.children; - if (children) { - for (let j = 0; j < children.length; j++) { - let child = nodes[children[j]]; - child.parent = node; - } + nodes: { + get () { + return this._nodes; } - } - }, - - _createVB(bin, gltf, accessors, attributes) { - // create vertex-format - let vfmt = []; - let vcount = 0; - - let byteOffset = 10e7, maxByteOffset = 0; - - if (attributes.POSITION !== undefined) { - let acc = accessors[attributes.POSITION]; - let vbView = gltf.bufferViews[acc.bufferView]; - vcount = acc.count; - - vfmt.push({ - name: gfx.ATTR_POSITION, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.NORMAL !== undefined) { - let acc = accessors[attributes.NORMAL]; - let vbView = gltf.bufferViews[acc.bufferView]; - - vfmt.push({ - name: gfx.ATTR_NORMAL, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.TANGENT !== undefined) { - let acc = accessors[attributes.TANGENT]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_TANGENT, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.COLOR_0 !== undefined) { - let acc = accessors[attributes.COLOR_0]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_COLOR0, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.TEXCOORD_0 !== undefined) { - let acc = accessors[attributes.TEXCOORD_0]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_UV0, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.TEXCOORD_1 !== undefined) { - let acc = accessors[attributes.TEXCOORD_1]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_UV1, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.TEXCOORD_2 !== undefined) { - let acc = accessors[attributes.TEXCOORD_2]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_UV2, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.TEXCOORD_3 !== undefined) { - let acc = accessors[attributes.TEXCOORD_3]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_UV3, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.JOINTS_0 !== undefined) { - let acc = accessors[attributes.JOINTS_0]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_JOINTS, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - if (attributes.WEIGHTS_0 !== undefined) { - let acc = accessors[attributes.WEIGHTS_0]; - let vbView = gltf.bufferViews[acc.bufferView]; - vfmt.push({ - name: gfx.ATTR_WEIGHTS, type: acc.componentType, num: _type2size[acc.type], - byteLength: vbView.byteLength, byteOffset: vbView.byteOffset - }); - - byteOffset = Math.min(byteOffset, vbView.byteOffset); - maxByteOffset = Math.max(maxByteOffset, vbView.byteOffset + vbView.byteLength); - } - - let gfxVFmt = new gfx.VertexFormat(vfmt); - let els = gfxVFmt._elements; - for (let i = 0; i < els.length; i++) { - let el = els[i]; - el.offset = vfmt[i].byteOffset - byteOffset; - el.stride = el.bytes; - } - - // create vertex-buffer - let vbData = new Uint8Array(bin, byteOffset, maxByteOffset - byteOffset); - let vb = new gfx.VertexBuffer( - renderer.device, - gfxVFmt, - gfx.USAGE_STATIC, - vbData, - vcount - ); - - return { - buffer: vb, - data: vbData - }; - }, - - createSkinning(index) { - let gltf = this._gltf; - let bin = this._bin; - if (index >= gltf.skins.length) { - return null; - } - - let gltfSkin = gltf.skins[index]; - - // extract bindposes mat4 data - let accessor = gltf.accessors[gltfSkin.inverseBindMatrices]; - let bufView = gltf.bufferViews[accessor.bufferView]; - let data = new Float32Array(bin, bufView.byteOffset + accessor.byteOffset, accessor.count * 16); - let bindposes = new Array(accessor.count); - - for (let i = 0; i < accessor.count; ++i) { - bindposes[i] = cc.vmath.mat4.new( - data[16 * i + 0], data[16 * i + 1], data[16 * i + 2], data[16 * i + 3], - data[16 * i + 4], data[16 * i + 5], data[16 * i + 6], data[16 * i + 7], - data[16 * i + 8], data[16 * i + 9], data[16 * i + 10], data[16 * i + 11], - data[16 * i + 12], data[16 * i + 13], data[16 * i + 14], data[16 * i + 15] - ); - } - - return { - jointIndices: gltfSkin.joints, - bindposes: bindposes, - }; - }, - - initMesh(meshAsset) { - const index = meshAsset._meshID; - - const bin = this._bin; - const gltf = this._gltf; - const gltfMesh = gltf.meshes[index]; - const accessors = gltf.accessors; - - // create index-buffer - let length = gltfMesh.primitives.length; - meshAsset._ibs.length = length; - meshAsset._vbs.length = length; - meshAsset._subMeshes.length = length; - for (let i = 0; i < length; ++i) { - let primitive = gltfMesh.primitives[i]; - - if (primitive.indices === undefined) continue; - - let vb = this._createVB(bin, gltf, accessors, primitive.attributes); - - let ibAcc = accessors[primitive.indices]; - let ibView = gltf.bufferViews[ibAcc.bufferView]; - let ibData = new Uint16Array(bin, ibView.byteOffset, ibView.byteLength/2); - let ibBuffer = new gfx.IndexBuffer( - renderer.device, - ibAcc.componentType, - gfx.USAGE_STATIC, - ibData, - ibAcc.count - ); - - let ib = { - buffer: ibBuffer, - data: ibData + }, + rootNode: { + get () { + return this._rootNode; } + }, - meshAsset._subMeshes[i] = new renderEngine.InputAssembler(vb.buffer, ibBuffer); - meshAsset._vbs[i] = vb; - meshAsset._ibs[i] = ib; + precomputeJointMatrix: { + get () { + return this._precomputeJointMatrix; + } } }, - initAnimationClip(clip) { - let gltf = this._gltf; - let bin = this._bin; - - let accessors = gltf.accessors; - let gltfAnimation = gltf.animations[clip._animationID]; - - clip.name = gltfAnimation.name; - clip.wrapMode = cc.WrapMode.Loop; - let duration = 0; - - let curveData = clip.curveData; - let paths = curveData.paths = {}; - - let nodes = gltf.nodes; - let rootNode = nodes[0]; - - let samplers = gltfAnimation.samplers; - let channels = gltfAnimation.channels; - for (let i = 0; i < channels.length; ++i) { - let gltfChannel = channels[i]; - let sampler = samplers[gltfChannel.sampler]; - - let inputArray = createArray(gltf, bin, sampler.input); - let outputArray = createArray(gltf, bin, sampler.output); - - let interpolation = sampler.interpolation; + onLoad () { + let nodes = this._nodes; + this._rootNode = nodes[0]; + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i]; + node.position = cc.v3.apply(this, node.position); + node.scale = cc.v3.apply(this, node.scale); + node.quat = cc.quat.apply(this, node.quat); - let target = gltfChannel.target; - let node = nodes[target.node]; - - let path = node.path; - - let curves; - if (path === '') { - curves = curveData; - } - else { - if (!paths[path]) { - paths[path] = {}; - } - curves = paths[path]; + if (node.uniqueBindPose) { + node.uniqueBindPose = cc.mat4.apply(this, node.uniqueBindPose); } - if (!curves.props) { - curves.props = {}; - } - - let frames = []; - for (let frameIdx = 0; frameIdx < inputArray.length; frameIdx++) { - let frame = inputArray[frameIdx]; - if (frame > duration) { - duration = frame; - } - frames.push({frame: frame}); - } - if (target.path === 'translation') { - for (let frameIdx = 0; frameIdx < inputArray.length; frameIdx++) { - let i = frameIdx * 3; - frames[frameIdx].value = cc.v3(outputArray[i], outputArray[i+1], outputArray[i+2]); + let pose = node.bindpose; + if (pose) { + for (let i in pose) { + pose[i] = cc.mat4.apply(this, pose[i]); } - curves.props.position = frames; - } - else if (target.path === 'rotation') { - for (let frameIdx = 0; frameIdx < inputArray.length; frameIdx++) { - let i = frameIdx * 4; - frames[frameIdx].value = cc.quat(outputArray[i], outputArray[i+1], outputArray[i+2], outputArray[i+3]); - } - curves.props.quat = frames; - } - else if (target.path === 'scale') { - for (let frameIdx = 0; frameIdx < inputArray.length; frameIdx++) { - let i = frameIdx * 3; - frames[frameIdx].value = cc.v3(outputArray[i], outputArray[i+1], outputArray[i+2]); - } - curves.props.scale = frames; - } - } - - for (let i = 1; i < nodes.length; i++) { - let node = nodes[i]; - if (paths[node.path]) continue; - - let curves = paths[node.path] = { props: {} }; - let props = curves.props; - - let rotation = node.rotation; - if (rotation) { - props.quat = [{ - frame: 0, - value: cc.quat(rotation[0], rotation[1], rotation[2], rotation[3]) - }]; } - let scale = node.scale; - if (scale) { - props.scale = [{ - frame: 0, - value: cc.v3(scale[0], scale[1], scale[2]) - }]; - } - - let translation = node.translation; - if (translation) { - props.position = [{ - frame: 0, - value: cc.v3(translation[0], translation[1], translation[2]) - }]; + let children = node.children; + if (children) { + for (let i = 0; i < children.length; i++) { + children[i] = nodes[children[i]]; + } } } - - clip._duration = duration; } }); -module.exports = Model; +cc.Model = module.exports = Model; diff --git a/cocos2d/core/3d/CCSkinnedMeshRenderer.js b/cocos2d/core/3d/CCSkinnedMeshRenderer.js deleted file mode 100644 index 960eea630d4..00000000000 --- a/cocos2d/core/3d/CCSkinnedMeshRenderer.js +++ /dev/null @@ -1,184 +0,0 @@ -/**************************************************************************** - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - http://www.cocos.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated engine source code (the "Software"), a limited, - worldwide, royalty-free, non-assignable, revocable and non-exclusive license - to use Cocos Creator solely to develop games on your target platforms. You shall - not use Cocos Creator software for developing other software or tools that's - used for developing games. You are not granted to publish, distribute, - sublicense, and/or sell copies of Cocos Creator. - - The software or tools in this License Agreement are licensed, not sold. - Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ - -const MeshRenderer = require('../mesh/CCMeshRenderer'); -const renderEngine = require('../renderer/render-engine'); -const mat4 = cc.vmath.mat4; - -let _m4_tmp = mat4.create(); - -let SkinnedMeshRenderer = cc.Class({ - name: 'cc.SkinnedMeshRenderer', - extends: MeshRenderer, - - ctor() { - this._jointsTextureData = null; - this._jointsTexture = null; - this._skinning = null; - this._matrices = []; - - this._assembler = MeshRenderer._assembler; - }, - - properties: { - _joints: [cc.Node], - _skinID: -1 - }, - - _createMaterial() { - let material = new renderEngine.MeshMaterial(); - material.color = cc.Color.WHITE; - if (cc.macro.ENABLE_3D) { - material._mainTech._passes[0].setDepth(true, true); - } - material.useModel = false; - material.useSkinning = true; - - if (this._jointsTexture) { - material.useJointsTexture = true; - material.jointsTexture = this._jointsTexture; - material.jointsTextureSize = this._jointsTexture.width; - } - else { - material.useJointsTexture = false; - material.jointMatrices = this._jointsTextureData; - } - - return material; - }, - - _reset() { - this._super(); - this._initSkinning(); - this._initJointsTexture(); - this._initMatrices(); - }, - - _initSkinning() { - if (this._skinID === -1 || !this._mesh || !this._mesh._model) return; - this._skinning = this._mesh._model.createSkinning(this._skinID); - }, - - _initJointsTexture() { - if (this._jointsTexture) { - this._jointsTexture.destroy(); - this._jointsTexture = null; - } - - let skinning = this._skinning; - if (!skinning) return; - - let jointCount = skinning.jointIndices.length; - - let ALLOW_FLOAT_TEXTURE = !!cc.renderer.device.ext('OES_texture_float'); - if (ALLOW_FLOAT_TEXTURE) { - // set jointsTexture - let size; - if (jointCount > 256) { - size = 64; - } else if (jointCount > 64) { - size = 32; - } else if (jointCount > 16) { - size = 16; - } else { - size = 8; - } - - this._jointsTextureData = new Float32Array(size * size * 4); - - let texture = new cc.Texture2D(); - texture.initWithData(this._jointsTextureData, cc.Texture2D.PixelFormat.RGBA32F, size, size); - - this._jointsTexture = texture; - } - else { - this._jointsTextureData = new Float32Array(jointCount * 16); - } - }, - - _initMatrices() { - let joints = this._joints; - let matrices = this._matrices; - - matrices.length = 0; - for (let i = 0; i < joints.length; i++) { - matrices.push(mat4.create()); - } - - this.updateMatrices(); - }, - - _setJointsTextureData(iMatrix, matrix) { - let arr = this._jointsTextureData; - arr[16 * iMatrix + 0] = matrix.m00; - arr[16 * iMatrix + 1] = matrix.m01; - arr[16 * iMatrix + 2] = matrix.m02; - arr[16 * iMatrix + 3] = matrix.m03; - arr[16 * iMatrix + 4] = matrix.m04; - arr[16 * iMatrix + 5] = matrix.m05; - arr[16 * iMatrix + 6] = matrix.m06; - arr[16 * iMatrix + 7] = matrix.m07; - arr[16 * iMatrix + 8] = matrix.m08; - arr[16 * iMatrix + 9] = matrix.m09; - arr[16 * iMatrix + 10] = matrix.m10; - arr[16 * iMatrix + 11] = matrix.m11; - arr[16 * iMatrix + 12] = matrix.m12; - arr[16 * iMatrix + 13] = matrix.m13; - arr[16 * iMatrix + 14] = matrix.m14; - arr[16 * iMatrix + 15] = matrix.m15; - }, - - _commitJointsData () { - if (this._jointsTexture) { - this._jointsTexture.update({image: this._jointsTextureData}); - } - }, - - updateMatrices() { - for (let i = 0; i < this._joints.length; ++i) { - this._joints[i].getWorldMatrix(this._matrices[i]); - } - }, - - update() { - if (!this._skinning) return; - const bindposes = this._skinning.bindposes; - const matrices = this._matrices; - - this.updateMatrices(); - - for (let i = 0; i < bindposes.length; ++i) { - let bindpose = bindposes[i]; - let worldMatrix = matrices[i]; - - mat4.multiply(_m4_tmp, worldMatrix, bindpose); - this._setJointsTextureData(i, _m4_tmp); - } - - this._commitJointsData(); - } -}); - -cc.SkinnedMeshRenderer = module.exports = SkinnedMeshRenderer; diff --git a/cocos2d/core/3d/actions.js b/cocos2d/core/3d/actions.js new file mode 100644 index 00000000000..cf3710fc52c --- /dev/null +++ b/cocos2d/core/3d/actions.js @@ -0,0 +1,214 @@ + +import Quat from '../value-types/quat'; +import Vec3 from '../value-types/vec3'; + +let _quat_tmp = cc.quat(); +let _vec3_tmp = cc.v3(); + +/* + * Rotates a Node object to a certain angle by modifying its quaternion property.
+ * The direction will be decided by the shortest angle. + * @class Rotate3DTo + * @extends ActionInterval + * @param {Number} duration duration in seconds + * @param {Number|Vec3} dstAngleX dstAngleX in degrees. + * @param {Number} [dstAngleY] dstAngleY in degrees. + * @param {Number} [dstAngleZ] dstAngleZ in degrees. + * @example + * var rotate3DTo = new cc.Rotate3DTo(2, cc.v3(0, 180, 0)); + */ +cc.Rotate3DTo = cc.Class({ + name: 'cc.Rotate3DTo', + extends: cc.ActionInterval, + + ctor:function (duration, dstAngleX, dstAngleY, dstAngleZ) { + this._startQuat = cc.quat(); + this._dstQuat = cc.quat(); + + dstAngleX !== undefined && this.initWithDuration(duration, dstAngleX, dstAngleY, dstAngleZ); + }, + + /* + * Initializes the action. + * @param {Number} duration + * @param {Number|Vec3|Quat} dstAngleX + * @param {Number} dstAngleY + * @param {Number} dstAngleZ + * @return {Boolean} + */ + initWithDuration:function (duration, dstAngleX, dstAngleY, dstAngleZ) { + if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) { + let dstQuat = this._dstQuat; + if (dstAngleX instanceof cc.Quat) { + dstQuat.set(dstAngleX); + } + else { + if (dstAngleX instanceof cc.Vec3) { + dstAngleY = dstAngleX.y; + dstAngleZ = dstAngleX.z; + dstAngleX = dstAngleX.x; + } + else { + dstAngleY = dstAngleY || 0; + dstAngleZ = dstAngleZ || 0; + } + Quat.fromEuler(dstQuat, dstAngleX, dstAngleY, dstAngleZ); + } + return true; + } + return false; + }, + + clone:function () { + var action = new cc.Rotate3DTo(); + this._cloneDecoration(action); + action.initWithDuration(this._duration, this._dstQuat); + return action; + }, + + startWithTarget:function (target) { + cc.ActionInterval.prototype.startWithTarget.call(this, target); + this._startQuat.set(target.quat); + }, + + reverse:function () { + cc.logID(1016); + }, + + update:function (dt) { + dt = this._computeEaseTime(dt); + if (this.target) { + Quat.slerp(_quat_tmp, this._startQuat, this._dstQuat, dt); + this.target.setRotation(_quat_tmp); + } + } +}); + +/** + * !#en + * Rotates a Node object to a certain angle by modifying its quternion property.
+ * The direction will be decided by the shortest angle. + * !#zh 旋转到目标角度,通过逐帧修改它的 quternion 属性,旋转方向将由最短的角度决定。 + * @method rotate3DTo + * @param {Number} duration duration in seconds + * @param {Number|Vec3|Quat} dstAngleX dstAngleX in degrees. + * @param {Number} [dstAngleY] dstAngleY in degrees. + * @param {Number} [dstAngleZ] dstAngleZ in degrees. + * @return {ActionInterval} + * @example + * // example + * var rotate3DTo = cc.rotate3DTo(2, cc.v3(0, 180, 0)); + */ +cc.rotate3DTo = function (duration, dstAngleX, dstAngleY, dstAngleZ) { + return new cc.Rotate3DTo(duration, dstAngleX, dstAngleY, dstAngleZ); +}; + + +/* + * Rotates a Node object counter clockwise a number of degrees by modifying its quaternion property. + * Relative to its properties to modify. + * @class Rotate3DBy + * @extends ActionInterval + * @param {Number} duration duration in seconds + * @param {Number|Vec3} deltaAngleX deltaAngleX in degrees + * @param {Number} [deltaAngleY] deltaAngleY in degrees + * @param {Number} [deltaAngleZ] deltaAngleZ in degrees + * @example + * var actionBy = new cc.Rotate3DBy(2, cc.v3(0, 360, 0)); + */ +cc.Rotate3DBy = cc.Class({ + name: 'cc.Rotate3DBy', + extends: cc.ActionInterval, + + ctor: function (duration, deltaAngleX, deltaAngleY, deltaAngleZ) { + this._startQuat = cc.quat(); + this._dstQuat = cc.quat(); + this._deltaAngle = cc.v3(); + deltaAngleX !== undefined && this.initWithDuration(duration, deltaAngleX, deltaAngleY, deltaAngleZ); + }, + + /* + * Initializes the action. + * @param {Number} duration duration in seconds + * @param {Number|Vec3} deltaAngleX deltaAngleX in degrees + * @param {Number} [deltaAngleY=] deltaAngleY in degrees + * @param {Number} [deltaAngleZ=] deltaAngleZ in degrees + * @return {Boolean} + */ + initWithDuration:function (duration, deltaAngleX, deltaAngleY, deltaAngleZ) { + if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) { + if (deltaAngleX instanceof cc.Vec3) { + deltaAngleY = deltaAngleX.y; + deltaAngleZ = deltaAngleX.z; + deltaAngleX = deltaAngleX.x; + } + else { + deltaAngleY = deltaAngleY || 0; + deltaAngleZ = deltaAngleZ || 0; + } + + Vec3.set(this._deltaAngle, deltaAngleX, deltaAngleY, deltaAngleZ); + return true; + } + return false; + }, + + clone:function () { + var action = new cc.Rotate3DBy(); + this._cloneDecoration(action); + action.initWithDuration(this._duration, this._angle); + return action; + }, + + startWithTarget:function (target) { + cc.ActionInterval.prototype.startWithTarget.call(this, target); + + let startAngle = target.eulerAngles; + let deltaAngle = this._deltaAngle; + Quat.fromEuler(this._dstQuat, startAngle.x + deltaAngle.x, startAngle.y + deltaAngle.y, startAngle.z + deltaAngle.z); + + this._startQuat.set(target.quat); + }, + + update: (function(){ + let RAD = Math.PI / 180; + return function (dt) { + dt = this._computeEaseTime(dt); + if (this.target) { + Quat.slerp(_quat_tmp, this._startQuat, this._dstQuat, dt); + this.target.setRotation(_quat_tmp); + } + } + })(), + + reverse:function () { + let angle = this._angle; + _vec3_tmp.x = -angle.x; + _vec3_tmp.y = -angle.y; + _vec3_tmp.z = -angle.z; + var action = new cc.Rotate3DBy(this._duration, _vec3_tmp); + this._cloneDecoration(action); + this._reverseEaseList(action); + return action; + } +}); + +/** + * !#en + * Rotates a Node object counter clockwise a number of degrees by modifying its quaternion property. + * Relative to its properties to modify. + * !#zh 旋转指定的 3D 角度。 + * @method rotate3DBy + * @param {Number} duration duration in seconds + * @param {Number|Vec3} deltaAngleX deltaAngleX in degrees + * @param {Number} [deltaAngleY] deltaAngleY in degrees + * @param {Number} [deltaAngleZ] deltaAngleZ in degrees + * @return {ActionInterval} + * @example + * // example + * var actionBy = cc.rotate3DBy(2, cc.v3(0, 360, 0)); + */ +cc.rotate3DBy = function (duration, deltaAngleX, deltaAngleY, deltaAngleZ) { + return new cc.Rotate3DBy(duration, deltaAngleX, deltaAngleY, deltaAngleZ); +}; + diff --git a/cocos2d/core/3d/index.js b/cocos2d/core/3d/index.js index 3d1a2c9df34..a35b7bf3c52 100644 --- a/cocos2d/core/3d/index.js +++ b/cocos2d/core/3d/index.js @@ -1,9 +1,22 @@ -if (!CC_TEST) { - require('./polyfill-3d'); +if (!CC_TEST && (!CC_EDITOR || !Editor.isMainProcess)) { + require('./primitive'); + require('./physics/exports/physics-builtin'); + require('./physics/exports/physics-cannon'); + require('./physics/exports/physics-framework'); } require('./CCModel'); -require('./CCSkeletonAnimationClip'); -require('./CCSkeletonAnimation'); -require('./CCSkinnedMeshRenderer'); +require('./skeleton/CCSkeleton'); +require('./skeleton/CCSkeletonAnimationClip'); +require('./actions'); +require('./physics/framework/assets/physics-material'); + +if (!CC_EDITOR || !Editor.isMainProcess) { + require('./skeleton/CCSkeletonAnimation'); + require('./skeleton/CCSkinnedMeshRenderer'); + require('./skeleton/skinned-mesh-renderer'); + require('./CCLightComponent'); + require('./particle/particle-system-3d'); + require('./particle/renderer/particle-system-3d-renderer'); +} diff --git a/cocos2d/core/3d/particle/animator/color-overtime.ts b/cocos2d/core/3d/particle/animator/color-overtime.ts new file mode 100644 index 00000000000..dc3528123d4 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/color-overtime.ts @@ -0,0 +1,39 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator' +import { pseudoRandom, Color } from '../../../value-types'; +import GradientRange from './gradient-range'; + +const COLOR_OVERTIME_RAND_OFFSET = 91041; + +/** + * !#en The color over time module of 3d particle. + * !#zh 3D 粒子颜色变化模块 + * @class ColorOvertimeModule + */ +@ccclass('cc.ColorOvertimeModule') +export default class ColorOvertimeModule { + + /** + * !#en The enable of ColorOvertimeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + /** + * !#en The parameter of color change over time, the linear difference between each key changes. + * !#zh 颜色随时间变化的参数,各个 key 之间线性差值变化。 + * @type {GradientRange} color + */ + @property({ + type: GradientRange, + }) + color = new GradientRange(); + + animate (particle) { + if (this.enable) { + particle.color.set(particle.startColor); + particle.color.multiply(this.color.evaluate(1.0 - particle.remainingLifetime / particle.startLifetime, pseudoRandom(particle.randomSeed + COLOR_OVERTIME_RAND_OFFSET))); + } + } +} diff --git a/cocos2d/core/3d/particle/animator/curve-range.ts b/cocos2d/core/3d/particle/animator/curve-range.ts new file mode 100644 index 00000000000..4a61e20cf16 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/curve-range.ts @@ -0,0 +1,136 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import Enum from '../../../platform/CCEnum'; +import { lerp } from '../../../value-types'; +import { AnimationCurve } from '../curve'; + +const SerializableTable = CC_EDITOR && [ + [ "mode", "constant", "multiplier" ], + [ "mode", "curve", "multiplier" ], + [ "mode", "curveMin", "curveMax", "multiplier" ], + [ "mode", "constantMin", "constantMax", "multiplier"] +]; + +export const Mode = Enum({ + Constant: 0, + Curve: 1, + TwoCurves: 2, + TwoConstants: 3, +}); + +/** + * !#en The curve range of target value. + * !#zh 目标值的曲线范围 + * @class CurveRange + */ +@ccclass('cc.CurveRange') +export default class CurveRange { + static Mode = Mode; + + /** + * !#en Curve type. + * !#zh 曲线类型。 + * @property {Mode} mode + */ + @property({ + type: Mode, + }) + mode = Mode.Constant; + + /** + * !#en The curve to use when mode is Curve. + * !#zh 当 mode 为 Curve 时,使用的曲线。 + * @property {AnimationCurve} curve + */ + @property({ + type: AnimationCurve, + }) + curve = new AnimationCurve(); + + /** + * !#en The lower limit of the curve to use when mode is TwoCurves + * !#zh 当 mode 为 TwoCurves 时,使用的曲线下限。 + * @property {AnimationCurve} curveMin + */ + @property({ + type: AnimationCurve, + }) + curveMin = new AnimationCurve(); + + /** + * !#en The upper limit of the curve to use when mode is TwoCurves + * !#zh 当 mode 为 TwoCurves 时,使用的曲线上限。 + * @property {AnimationCurve} curveMax + */ + @property({ + type: AnimationCurve, + }) + curveMax = new AnimationCurve(); + + /** + * !#en When mode is Constant, the value of the curve. + * !#zh 当 mode 为 Constant 时,曲线的值。 + * @property {Number} constant + */ + @property + constant = 0; + + /** + * !#en The lower limit of the curve to use when mode is TwoConstants + * !#zh 当 mode 为 TwoConstants 时,曲线的下限。 + * @property {Number} constantMin + */ + @property + constantMin = 0; + + + /** + * !#en The upper limit of the curve to use when mode is TwoConstants + * !#zh 当 mode 为 TwoConstants 时,曲线的上限。 + * @property {Number} constantMax + */ + @property + constantMax = 0; + + /** + * !#en Coefficients applied to curve interpolation. + * !#zh 应用于曲线插值的系数。 + * @property {Number} multiplier + */ + @property + multiplier = 1; + + constructor () { + + } + + evaluate (time, rndRatio) { + switch (this.mode) { + case Mode.Constant: + return this.constant; + case Mode.Curve: + return this.curve.evaluate(time) * this.multiplier; + case Mode.TwoCurves: + return lerp(this.curveMin.evaluate(time), this.curveMax.evaluate(time), rndRatio) * this.multiplier; + case Mode.TwoConstants: + return lerp(this.constantMin, this.constantMax, rndRatio); + } + } + + getMax () { + switch (this.mode) { + case Mode.Constant: + return this.constant; + case Mode.Curve: + return this.multiplier; + case Mode.TwoConstants: + return this.constantMax; + case Mode.TwoCurves: + return this.multiplier; + } + return 0; + } +} + +CC_EDITOR && (CurveRange.prototype._onBeforeSerialize = function(props){return SerializableTable[this.mode];}); + +cc.CurveRange = CurveRange; diff --git a/cocos2d/core/3d/particle/animator/force-overtime.ts b/cocos2d/core/3d/particle/animator/force-overtime.ts new file mode 100644 index 00000000000..746a4e70fa6 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/force-overtime.ts @@ -0,0 +1,96 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { pseudoRandom, Quat, Vec3 } from '../../../value-types'; +import { Space } from '../enum'; +import { calculateTransform } from '../particle-general-function'; +import CurveRange from './curve-range'; + +// tslint:disable: max-line-length +const FORCE_OVERTIME_RAND_OFFSET = 212165; + +const _temp_v3 = cc.v3(); + +/** + * !#en The force over time module of 3d particle. + * !#zh 3D 粒子的加速度模块 + * @class ForceOvertimeModule + */ +@ccclass('cc.ForceOvertimeModule') +export default class ForceOvertimeModule { + + /** + * !#en The enable of ColorOvertimeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + /** + * !#en Coordinate system used in acceleration calculation. + * !#zh 加速度计算时采用的坐标系。 + * @property {Space} space + */ + @property({ + type: Space, + }) + space = Space.Local; + + /** + * !#en X-axis acceleration component. + * !#zh X 轴方向上的加速度分量。 + * @property {CurveRange} x + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + x = new CurveRange(); + + /** + * !#en Y-axis acceleration component. + * !#zh Y 轴方向上的加速度分量。 + * @property {CurveRange} y + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + y = new CurveRange(); + + /** + * !#en Z-axis acceleration component. + * !#zh Z 轴方向上的加速度分量。 + * @property {CurveRange} z + */ + @property({ + type: CurveRange, + range: [-1, 1], + displayOrder: 4, + }) + z = new CurveRange(); + + // TODO:currently not supported + randomized = false; + + rotation = null; + needTransform = false; + + constructor () { + this.rotation = new Quat(); + this.needTransform = false; + } + + update (space, worldTransform) { + this.needTransform = calculateTransform(space, this.space, worldTransform, this.rotation); + } + + animate (p, dt) { + const normalizedTime = 1 - p.remainingLifetime / p.startLifetime; + const force = Vec3.set(_temp_v3, this.x.evaluate(normalizedTime, pseudoRandom(p.randomSeed + FORCE_OVERTIME_RAND_OFFSET)), this.y.evaluate(normalizedTime, pseudoRandom(p.randomSeed + FORCE_OVERTIME_RAND_OFFSET)), this.z.evaluate(normalizedTime, pseudoRandom(p.randomSeed + FORCE_OVERTIME_RAND_OFFSET))); + if (this.needTransform) { + Vec3.transformQuat(force, force, this.rotation); + } + Vec3.scaleAndAdd(p.velocity, p.velocity, force, dt); + } +} + diff --git a/cocos2d/core/3d/particle/animator/gradient-range.ts b/cocos2d/core/3d/particle/animator/gradient-range.ts new file mode 100644 index 00000000000..dae6a4aa52d --- /dev/null +++ b/cocos2d/core/3d/particle/animator/gradient-range.ts @@ -0,0 +1,147 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import Enum from '../../../platform/CCEnum'; +import { Color } from '../../../value-types'; +import { Gradient, AlphaKey, ColorKey } from './gradient'; + +const GRADIENT_MODE_FIX = 0; +const GRADIENT_MODE_BLEND = 1; + +const GRADIENT_RANGE_MODE_COLOR = 0; +const GRADIENT_RANGE_MODE_TWO_COLOR = 1; +const GRADIENT_RANGE_MODE_RANDOM_COLOR = 2; +const GRADIENT_RANGE_MODE_GRADIENT = 3; +const GRADIENT_RANGE_MODE_TWO_GRADIENT = 4; + +const SerializableTable = CC_EDITOR && [ + [ "_mode", "color" ], + [ "_mode", "gradient" ], + [ "_mode", "colorMin", "colorMax" ], + [ "_mode", "gradientMin", "gradientMax"], + [ "_mode", "gradient" ] +]; + +const Mode = Enum({ + Color: 0, + Gradient: 1, + TwoColors: 2, + TwoGradients: 3, + RandomColor: 4, +}); + +/** + * !#en The gradient range of color. + * !#zh 颜色值的渐变范围 + * @class GradientRange + */ +@ccclass('cc.GradientRange') +export default class GradientRange { + + static Mode = Mode; + + @property + _mode = Mode.Color; + /** + * !#en Gradient type. + * !#zh 渐变色类型。 + * @property {Mode} mode + */ + @property({ + type: Mode, + }) + get mode () { + return this._mode; + } + + set mode (m) { + if (CC_EDITOR) { + if (m === Mode.RandomColor) { + if (this.gradient.colorKeys.length === 0) { + this.gradient.colorKeys.push(new ColorKey()); + } + if (this.gradient.alphaKeys.length === 0) { + this.gradient.alphaKeys.push(new AlphaKey()); + } + } + } + this._mode = m; + } + + @property + _color = cc.Color.WHITE.clone(); + /** + * !#en The color when mode is Color. + * !#zh 当 mode 为 Color 时的颜色。 + * @property {Color} color + */ + @property + color = cc.Color.WHITE.clone(); + + /** + * !#en Lower color limit when mode is TwoColors. + * !#zh 当 mode 为 TwoColors 时的颜色下限。 + * @property {Color} colorMin + */ + @property + colorMin = cc.Color.WHITE.clone(); + + /** + * !#en Upper color limit when mode is TwoColors. + * !#zh 当 mode 为 TwoColors 时的颜色上限。 + * @property {Color} colorMax + */ + @property + colorMax = cc.Color.WHITE.clone(); + + /** + * !#en Color gradient when mode is Gradient + * !#zh 当 mode 为 Gradient 时的颜色渐变。 + * @property {Gradient} gradient + */ + @property({ + type: Gradient, + }) + gradient = new Gradient(); + + /** + * !#en Lower color gradient limit when mode is TwoGradients. + * !#zh 当 mode 为 TwoGradients 时的颜色渐变下限。 + * @property {Gradient} gradientMin + */ + @property({ + type: Gradient, + }) + gradientMin = new Gradient(); + + /** + * !#en Upper color gradient limit when mode is TwoGradients. + * !#zh 当 mode 为 TwoGradients 时的颜色渐变上限。 + * @property {Gradient} gradientMax + */ + @property({ + type: Gradient, + }) + gradientMax = new Gradient(); + + evaluate (time, rndRatio) { + switch (this._mode) { + case Mode.Color: + return this.color; + case Mode.TwoColors: + this.colorMin.lerp(this.colorMax, rndRatio, this._color); + return this._color; + case Mode.RandomColor: + return this.gradient.randomColor(); + case Mode.Gradient: + return this.gradient.evaluate(time); + case Mode.TwoGradients: + this.gradientMin.evaluate(time).lerp(this.gradientMax.evaluate(time), rndRatio, this._color); + return this._color; + default: + return this.color; + } + } +} + +CC_EDITOR && (GradientRange.prototype._onBeforeSerialize = function(props){return SerializableTable[this._mode];}); + +cc.GradientRange = GradientRange; diff --git a/cocos2d/core/3d/particle/animator/gradient.ts b/cocos2d/core/3d/particle/animator/gradient.ts new file mode 100644 index 00000000000..a13d2aa3855 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/gradient.ts @@ -0,0 +1,191 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import Enum from '../../../platform/CCEnum'; +import { lerp, repeat } from '../../../value-types'; + +// tslint:disable: max-line-length + +const Mode = Enum({ + Blend: 0, + Fixed: 1, +}); + +/** + * !#en The color key of gradient. + * !#zh color 关键帧 + * @class ColorKey + */ +@ccclass('cc.ColorKey') +export class ColorKey { + /** + * !#en Color value. + * !#zh 颜色值。 + * @property {Color} color + */ + @property + color = cc.Color.WHITE.clone(); + /** + * !#en Time value. + * !#zh 时间值。 + * @property {Number} time + */ + @property + time = 0; +} + +/** + * !#en The alpha key of gradient. + * !#zh alpha 关键帧 + * @class AlphaKey + */ +@ccclass('cc.AlphaKey') +export class AlphaKey { + /** + * !#en Alpha value. + * !#zh 透明度。 + * @property {Number} alpha + */ + @property + alpha = 1; + /** + * !#en Time. + * !#zh 时间帧。 + * @property {Number} time + */ + @property + time = 0; +} + +/** + * !#en The gradient data of color. + * !#zh 颜色渐变数据 + * @class Gradient + */ +@ccclass('cc.Gradient') +export class Gradient { + + static Mode = Mode; + /** + * !#en Array of color key. + * !#zh 颜色关键帧列表。 + * @property {[ColorKey]} colorKeys + */ + @property({ + type: [ColorKey], + }) + colorKeys = new Array(); + /** + * !#en Array of alpha key. + * !#zh 透明度关键帧列表。 + * @property {[AlphaKey]} alphaKeys + */ + @property({ + type: [AlphaKey], + }) + alphaKeys = new Array(); + /** + * !#en Blend mode. + * !#zh 混合模式。 + * @property {Mode} mode + */ + @property({ + type: Mode, + }) + mode = Mode.Blend; + + _color = null; + + constructor () { + this._color = cc.Color.WHITE.clone(); + } + + setKeys (colorKeys, alphaKeys) { + this.colorKeys = colorKeys; + this.alphaKeys = alphaKeys; + } + + sortKeys () { + if (this.colorKeys.length > 1) { + this.colorKeys.sort((a, b) => a.time - b.time); + } + if (this.alphaKeys.length > 1) { + this.alphaKeys.sort((a, b) => a.time - b.time); + } + } + + evaluate (time) { + this.getRGB(time); + this._color._fastSetA(this.getAlpha(time)); + return this._color; + } + + randomColor () { + const c = this.colorKeys[Math.trunc(Math.random() * this.colorKeys.length)]; + const a = this.alphaKeys[Math.trunc(Math.random() * this.alphaKeys.length)]; + this._color.set(c.color); + this._color._fastSetA(a.alpha); + return this._color; + } + + getRGB (time) { + if (this.colorKeys.length > 1) { + time = repeat(time, 1); + for (let i = 1; i < this.colorKeys.length; ++i) { + const preTime = this.colorKeys[i - 1].time; + const curTime = this.colorKeys[i].time; + if (time >= preTime && time < curTime) { + if (this.mode === Mode.Fixed) { + return this.colorKeys[i].color; + } + const factor = (time - preTime) / (curTime - preTime); + this.colorKeys[i - 1].color.lerp(this.colorKeys[i].color, factor, this._color); + return this._color; + } + } + const lastIndex = this.colorKeys.length - 1; + if (time < this.colorKeys[0].time) { + cc.Color.BLACK.lerp(this.colorKeys[0].color, time / this.colorKeys[0].time, this._color); + } else if (time > this.colorKeys[lastIndex].time) { + this.colorKeys[lastIndex].color.lerp(cc.Color.BLACK, (time - this.colorKeys[lastIndex].time) / (1 - this.colorKeys[lastIndex].time), this._color); + } + // console.warn('something went wrong. can not get gradient color.'); + } else if (this.colorKeys.length === 1) { + this._color.set(this.colorKeys[0].color); + return this._color; + } else { + this._color.set(cc.Color.WHITE); + return this._color; + } + } + + getAlpha (time) { + if (this.alphaKeys.length > 1) { + time = repeat(time, 1); + for (let i = 1; i < this.alphaKeys.length; ++i) { + const preTime = this.alphaKeys[i - 1].time; + const curTime = this.alphaKeys[i].time; + if (time >= preTime && time < curTime) { + if (this.mode === Mode.Fixed) { + return this.alphaKeys[i].alpha; + } + const factor = (time - preTime) / (curTime - preTime); + return lerp(this.alphaKeys[i - 1].alpha , this.alphaKeys[i].alpha , factor); + } + } + const lastIndex = this.alphaKeys.length - 1; + if (time < this.alphaKeys[0].time) { + return lerp(255, this.alphaKeys[0].alpha, time / this.alphaKeys[0].time); + } else if (time > this.alphaKeys[lastIndex].time) { + return lerp(this.alphaKeys[lastIndex].alpha, 255, (time - this.alphaKeys[lastIndex].time) / (1 - this.alphaKeys[lastIndex].time)); + } + } else if (this.alphaKeys.length === 1) { + return this.alphaKeys[0].alpha; + } else { + return 255; + } + } +} + +cc.ColorKey = ColorKey; +cc.AlphaKey = AlphaKey; +cc.Gradient = Gradient; + diff --git a/cocos2d/core/3d/particle/animator/limit-velocity-overtime.ts b/cocos2d/core/3d/particle/animator/limit-velocity-overtime.ts new file mode 100644 index 00000000000..84618c4f853 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/limit-velocity-overtime.ts @@ -0,0 +1,149 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { lerp, pseudoRandom, Vec3, Quat } from '../../../value-types'; +import { Space } from '../enum'; +import CurveRange from './curve-range'; + +// tslint:disable: max-line-length +const LIMIT_VELOCITY_RAND_OFFSET = 23541; + +const _temp_v3 = cc.v3(); +const _temp_v3_1 = cc.v3(); + +function dampenBeyondLimit (vel, limit, dampen) { + const sgn = Math.sign(vel); + let abs = Math.abs(vel); + if (abs > limit) { + abs = lerp(abs, limit, dampen); + } + return abs * sgn; +} + +/** + * !#en The limit velocity module of 3d particle. + * !#zh 3D 粒子的限速模块 + * @class LimitVelocityOvertimeModule + */ +@ccclass('cc.LimitVelocityOvertimeModule') +export default class LimitVelocityOvertimeModule { + + /** + * !#en The enable of LimitVelocityOvertimeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + /** + * !#en The coordinate system used when calculating the lower speed limit. + * !#zh 计算速度下限时采用的坐标系。 + * @property {Space} space + */ + @property({ + type: Space, + }) + space = Space.Local; + + /** + * !#en Whether to limit the three axes separately. + * !#zh 是否三个轴分开限制。 + * @property {Boolean} separateAxes + */ + @property + separateAxes = false; + + /** + * !#en Lower speed limit + * !#zh 速度下限。 + * @property {CurveRange} limit + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + limit = new CurveRange(); + + /** + * !#en Lower speed limit in X direction. + * !#zh X 轴方向上的速度下限。 + * @property {CurveRange} limitX + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + limitX = new CurveRange(); + + /** + * !#en Lower speed limit in Y direction. + * !#zh Y 轴方向上的速度下限。 + * @property {CurveRange} limitY + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + limitY = new CurveRange(); + + /** + * !#en Lower speed limit in Z direction. + * !#zh Z 轴方向上的速度下限。 + * @property {CurveRange} limitZ + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + limitZ = new CurveRange(); + + /** + * !#en Interpolation of current speed and lower speed limit. + * !#zh 当前速度与速度下限的插值。 + * @property {Number} dampen + */ + @property + dampen = 3; + + // TODO:functions related to drag are temporarily not supported + drag = null; + + multiplyDragByParticleSize = false; + + multiplyDragByParticleVelocity = false; + + private rotation = null; + private needTransform = false; + + constructor () { + this.rotation = new Quat(); + this.needTransform = false; + } + + update (space: number, worldTransform: Mat4) { + this.needTransform = calculateTransform(space, this.space, worldTransform, this.rotation); + } + + animate (p) { + const normalizedTime = 1 - p.remainingLifetime / p.startLifetime; + const dampedVel = _temp_v3; + if (this.separateAxes) { + Vec3.set(_temp_v3_1, this.limitX.evaluate(normalizedTime, pseudoRandom(p.randomSeed + LIMIT_VELOCITY_RAND_OFFSET))!, + this.limitY.evaluate(normalizedTime, pseudoRandom(p.randomSeed + LIMIT_VELOCITY_RAND_OFFSET))!, + this.limitZ.evaluate(normalizedTime, pseudoRandom(p.randomSeed + LIMIT_VELOCITY_RAND_OFFSET))!); + if (this.needTransform) { + Vec3.transformQuat(_temp_v3_1, _temp_v3_1, this.rotation); + } + Vec3.set(dampedVel, + dampenBeyondLimit(p.ultimateVelocity.x, _temp_v3_1.x, this.dampen), + dampenBeyondLimit(p.ultimateVelocity.y, _temp_v3_1.y, this.dampen), + dampenBeyondLimit(p.ultimateVelocity.z, _temp_v3_1.z, this.dampen)); + } + else { + Vec3.normalize(dampedVel, p.ultimateVelocity); + Vec3.scale(dampedVel, dampedVel, dampenBeyondLimit(p.ultimateVelocity.len(), this.limit.evaluate(normalizedTime, pseudoRandom(p.randomSeed + LIMIT_VELOCITY_RAND_OFFSET)), this.dampen)); + } + Vec3.copy(p.ultimateVelocity, dampedVel); + } + +} + diff --git a/cocos2d/core/3d/particle/animator/optimized-curve.ts b/cocos2d/core/3d/particle/animator/optimized-curve.ts new file mode 100644 index 00000000000..043e99f5ef0 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/optimized-curve.ts @@ -0,0 +1,203 @@ +import { repeat } from '../../../value-types'; +import { evalOptCurve, OptimizedKey } from '../curve'; +import { Mode } from './curve-range'; + +const CURVE_MODE_CONSTANT = 0; +const CURVE_MODE_RANDOM_CONSTANT = 1; +const CURVE_MODE_CURVE = 2; +const CURVE_MODE_RANDOM_CURVE = 3; + +const UNIFORM_CURVE_KEY_NUM = 8; + +// calculate the coefficience of the first order integral of the curve +function integrateKeyframe (coef) { + coef[0] = coef[0] / 4; + coef[1] = coef[1] / 3; + coef[2] = coef[2] / 2; + coef[3] = coef[3]; + return coef; +} + +// calculate the coefficience of the second order integral of the curve +function integrateKeyframeTwice (coef) { + coef[0] = coef[0] / 20; + coef[1] = coef[1] / 12; + coef[2] = coef[2] / 6; + coef[3] = coef[3] / 2; + return coef; +} + +/** + * !#en The optimized curve. + * !#zh 优化曲线 + * @class OptimizedCurve + */ +export class OptimizedCurve { + optimizedKeys = []; + integral = []; + constructUniform = false; + coefUniform = null; + timeUniform = null; + integralUniform = null; + + constructor (constructUniform = false) { + this.optimizedKeys = new Array(); // the i-th optimezed key stores coefficients of [i,i+1] segment in the original curve,so if the time of last key of the original key is 1,the last key won't be kept in the opt curve. + this.integral = new Array(); // the integral of the curve between 0 and corresponding key,the i-th integral corresponds to the i+1-th key in optimizedKeys (because the integral of the first key is always zero,the first key won't be stored) + this.constructUniform = constructUniform; + this.coefUniform = null; + this.timeUniform = null; + this.integralUniform = null; + } + + buildCurve (animationCurve, multiplier = 1) { + const keyNum = animationCurve.keyFrames.length - 1; + let i = 0; + if (this.optimizedKeys.length < keyNum) { + const keyToAdd = keyNum - this.optimizedKeys.length; + for (i = 0; i < keyToAdd; i++) { + const optKey = new OptimizedKey(); + this.optimizedKeys.push(optKey); + } + } else { + this.optimizedKeys.splice(keyNum); + } + if (animationCurve.keyFrames.length === 1) { + this.optimizedKeys[0].coefficient[3] = animationCurve.keyFrames[0].value * multiplier; + this.optimizedKeys[0].time = 0; + this.optimizedKeys[0].endTime = 1; + } else { + let keyOffset = 0; + if (animationCurve.keyFrames[0].time !== 0) { + this.optimizedKeys.splice(0, 0, new OptimizedKey()); + this.optimizedKeys[0].time = 0; + this.optimizedKeys[0].endTime = animationCurve.keyFrames[0].time; + this.optimizedKeys[0].coefficient[3] = animationCurve.keyFrames[0].value; + keyOffset = 1; + } + for (i = 0; i < keyNum; i++) { + animationCurve.calcOptimizedKey(this.optimizedKeys[i + keyOffset], i, Math.min(i + 1, keyNum)); + this.optimizedKeys[i + keyOffset].index += keyOffset; + } + if (animationCurve.keyFrames[animationCurve.keyFrames.length - 1].time !== 1) { + this.optimizedKeys.push(new OptimizedKey()); + this.optimizedKeys[this.optimizedKeys.length - 1].time = animationCurve.keyFrames[animationCurve.keyFrames.length - 1].time; + this.optimizedKeys[this.optimizedKeys.length - 1].endTime = 1; + this.optimizedKeys[this.optimizedKeys.length - 1].coefficient[3] = animationCurve.keyFrames[animationCurve.keyFrames.length - 1].value; + } + } + for (i = 0; i < this.optimizedKeys.length; i++) { + this.optimizedKeys[i].coefficient[0] *= multiplier; + this.optimizedKeys[i].coefficient[1] *= multiplier; + this.optimizedKeys[i].coefficient[2] *= multiplier; + this.optimizedKeys[i].coefficient[3] *= multiplier; + } + if (this.constructUniform) { + this.coefUniform = new Float32Array(UNIFORM_CURVE_KEY_NUM * 4); + this.timeUniform = new Float32Array(UNIFORM_CURVE_KEY_NUM); + this.updateKeyUniform(); + } + } + + evaluate (time) { + time = repeat(time, 1); + for (let i = 1; i < this.optimizedKeys.length; i++) { + if (time < this.optimizedKeys[i].time) { + return this.optimizedKeys[i - 1].evaluate(time); + } + } + return this.optimizedKeys[this.optimizedKeys.length - 1].evaluate(time); + } + + // calculate first order integral coefficients of all keys + integrateOnce () { + let i = 0; + if (this.integral.length + 1 < this.optimizedKeys.length) { + for (i = 0; i < this.optimizedKeys.length - this.integral.length - 1; i++) { + this.integral.push(0); + } + } else { + this.integral.splice(this.optimizedKeys.length - 1); + } + for (i = 0; i < this.integral.length; i++) { + integrateKeyframe(this.optimizedKeys[i].coefficient); + const deltaT = this.optimizedKeys[i + 1].time - this.optimizedKeys[i].time; + const prevIntegral = i === 0 ? 0 : this.integral[i - 1]; + this.integral[i] = prevIntegral + (deltaT * evalOptCurve(deltaT, this.optimizedKeys[i].coefficient)); + } + integrateKeyframe(this.optimizedKeys[this.optimizedKeys.length - 1].coefficient); + if (this.constructUniform) { + this.updateKeyUniform(); + this.updateIntegralUniform(); + } + } + + // get the integral of the curve using calculated coefficients + evaluateIntegral (t, ts = 1) { + t = repeat(t, 1); + for (let i = 1; i < this.optimizedKeys.length; i++) { + if (t < this.optimizedKeys[i].time) { + const prevInt = i === 1 ? 0 : this.integral[i - 2]; + const dt = t - this.optimizedKeys[i - 1].time; + return ts * (prevInt + (dt * evalOptCurve(dt, this.optimizedKeys[i - 1].coefficient))); + } + } + const dt = t - this.optimizedKeys[this.optimizedKeys.length - 1].time; + return ts * (this.integral[this.integral.length - 1] + (dt * evalOptCurve(dt, this.optimizedKeys[this.optimizedKeys.length - 1].coefficient))); + } + + // calculate second order integral coefficients of all keys + integrateTwice () { + let i = 0; + if (this.integral.length + 1 < this.optimizedKeys.length) { + for (i = 0; i < this.optimizedKeys.length - this.integral.length - 1; i++) { + this.integral.push(0); + } + } else { + this.integral.splice(this.optimizedKeys.length - 1); + } + for (i = 0; i < this.integral.length; i++) { + integrateKeyframeTwice(this.optimizedKeys[i].coefficient); + const deltaT = this.optimizedKeys[i + 1].time - this.optimizedKeys[i].time; + const prevIntegral = i === 0 ? 0 : this.integral[i - 1]; + this.integral[i] = prevIntegral + (deltaT * deltaT * evalOptCurve(deltaT, this.optimizedKeys[i].coefficient)); + } + integrateKeyframeTwice(this.optimizedKeys[this.optimizedKeys.length - 1].coefficient); + if (this.constructUniform) { + this.updateKeyUniform(); + this.updateIntegralUniform(); + } + } + + // get the second order integral of the curve using calculated coefficients + evaluateIntegralTwice (t, ts = 1) { + t = repeat(t, 1); + for (let i = 1; i < this.optimizedKeys.length; i++) { + if (t < this.optimizedKeys[i].time) { + const prevInt = i === 1 ? 0 : this.integral[i - 2]; + const dt = t - this.optimizedKeys[i - 1].time; + return ts * ts * (prevInt + (dt * dt * evalOptCurve(dt, this.optimizedKeys[i - 1].coefficient))); + } + } + const dt = t - this.optimizedKeys[this.optimizedKeys.length - 1].time; + return ts * ts * (this.integral[this.integral.length - 1] + (dt * dt * evalOptCurve(dt, this.optimizedKeys[this.optimizedKeys.length - 1].coefficient))); + } + + updateKeyUniform () { + if (this.coefUniform != null && this.timeUniform != null) { + for (let i = 0; i < this.optimizedKeys.length; i++) { + this.coefUniform[i * 4] = this.optimizedKeys[i].coefficient[0]; + this.coefUniform[i * 4 + 1] = this.optimizedKeys[i].coefficient[1]; + this.coefUniform[i * 4 + 2] = this.optimizedKeys[i].coefficient[2]; + this.coefUniform[i * 4 + 3] = this.optimizedKeys[i].coefficient[3]; + this.timeUniform[i] = this.optimizedKeys[i].endTime; + } + } + } + + updateIntegralUniform () { + this.integralUniform = new Float32Array(UNIFORM_CURVE_KEY_NUM - 1); + for (let i = 0; i < this.integral.length; i++) { + this.integralUniform[i] = this.integral[i]; + } + } +} diff --git a/cocos2d/core/3d/particle/animator/rotation-overtime.ts b/cocos2d/core/3d/particle/animator/rotation-overtime.ts new file mode 100644 index 00000000000..b20f4d02005 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/rotation-overtime.ts @@ -0,0 +1,99 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { pseudoRandom } from '../../../value-types'; +import CurveRange from './curve-range'; + +// tslint:disable: max-line-length +const ROTATION_OVERTIME_RAND_OFFSET = 125292; + +/** + * !#en The rotation module of 3d particle. + * !#zh 3D 粒子的旋转模块 + * @class RotationOvertimeModule + */ +@ccclass('cc.RotationOvertimeModule') +export default class RotationOvertimeModule { + + /** + * !#en The enable of RotationOvertimeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + @property + _separateAxes = false; + + /** + * !#en Whether to set the rotation of three axes separately (not currently supported) + * !#zh 是否三个轴分开设定旋转(暂不支持)。 + * @property {Boolean} separateAxes + */ + @property + get separateAxes () { + return this._separateAxes; + } + + set separateAxes (val) { + if (!val) { + this._separateAxes = val; + } + else { + console.error('rotation overtime separateAxes is not supported!'); + } + } + + /** + * !#en Set rotation around X axis. + * !#zh 绕 X 轴设定旋转。 + * @property {CurveRange} x + */ + @property({ + type: CurveRange, + range: [-1, 1], + radian: true, + }) + x = new CurveRange(); + + /** + * !#en Set rotation around Y axis. + * !#zh 绕 Y 轴设定旋转。 + * @property {CurveRange} y + */ + @property({ + type: CurveRange, + range: [-1, 1], + radian: true, + }) + y = new CurveRange(); + + /** + * !#en Set rotation around Z axis. + * !#zh 绕 Z 轴设定旋转。 + * @property {CurveRange} z + */ + @property({ + type: CurveRange, + range: [-1, 1], + radian: true, + }) + z = new CurveRange(); + + constructor () { + + } + + animate (p, dt) { + const normalizedTime = 1 - p.remainingLifetime / p.startLifetime; + if (!this._separateAxes) { + p.rotation.x += this.z.evaluate(normalizedTime, pseudoRandom(p.randomSeed + ROTATION_OVERTIME_RAND_OFFSET)) * dt; + } + else { + // TODO: separateAxes is temporarily not supported! + const rotationRand = pseudoRandom(p.randomSeed + ROTATION_OVERTIME_RAND_OFFSET); + p.rotation.x += this.x.evaluate(normalizedTime, rotationRand) * dt; + p.rotation.y += this.y.evaluate(normalizedTime, rotationRand) * dt; + p.rotation.z += this.z.evaluate(normalizedTime, rotationRand) * dt; + } + } +} diff --git a/cocos2d/core/3d/particle/animator/size-overtime.ts b/cocos2d/core/3d/particle/animator/size-overtime.ts new file mode 100644 index 00000000000..9641e43f13e --- /dev/null +++ b/cocos2d/core/3d/particle/animator/size-overtime.ts @@ -0,0 +1,83 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { pseudoRandom, Vec3 } from '../../../value-types'; +import CurveRange from './curve-range'; + +// tslint:disable: max-line-length +const SIZE_OVERTIME_RAND_OFFSET = 39825; + +/** + * !#en The size module of 3d particle. + * !#zh 3D 粒子的大小模块 + * @class SizeOvertimeModule + */ +@ccclass('cc.SizeOvertimeModule') +export default class SizeOvertimeModule { + + /** + * !#en The enable of SizeOvertimeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + /** + * !#en Decide whether to control particle size independently on each axis. + * !#zh 决定是否在每个轴上独立控制粒子大小。 + * @property {Boolean} separateAxes + */ + @property + separateAxes = false; + + /** + * !#en Define a curve to determine the size change of particles during their life cycle. + * !#zh 定义一条曲线来决定粒子在其生命周期中的大小变化。 + * @property {CurveRange} size + */ + @property({ + type: CurveRange, + }) + size = new CurveRange(); + + /** + * !#en Defines a curve to determine the size change of particles in the X-axis direction during their life cycle. + * !#zh 定义一条曲线来决定粒子在其生命周期中 X 轴方向上的大小变化。 + * @property {CurveRange} x + */ + @property({ + type: CurveRange, + }) + x = new CurveRange(); + + /** + * !#en Defines a curve to determine the size change of particles in the Y-axis direction during their life cycle. + * !#zh 定义一条曲线来决定粒子在其生命周期中 Y 轴方向上的大小变化。 + * @property {CurveRange} y + */ + @property({ + type: CurveRange, + }) + y = new CurveRange(); + + /** + * !#en Defines a curve to determine the size change of particles in the Z-axis direction during their life cycle. + * !#zh 定义一条曲线来决定粒子在其生命周期中 Z 轴方向上的大小变化。 + * @property {CurveRange} z + */ + @property({ + type: CurveRange, + }) + z = new CurveRange(); + + animate (particle) { + if (!this.separateAxes) { + Vec3.scale(particle.size, particle.startSize, this.size.evaluate(1 - particle.remainingLifetime / particle.startLifetime, pseudoRandom(particle.randomSeed + SIZE_OVERTIME_RAND_OFFSET))); + } else { + const currLifetime = 1 - particle.remainingLifetime / particle.startLifetime; + const sizeRand = pseudoRandom(particle.randomSeed + SIZE_OVERTIME_RAND_OFFSET); + particle.size.x = particle.startSize.x * this.x.evaluate(currLifetime, sizeRand); + particle.size.y = particle.startSize.y * this.y.evaluate(currLifetime, sizeRand); + particle.size.z = particle.startSize.z * this.z.evaluate(currLifetime, sizeRand); + } + } +} diff --git a/cocos2d/core/3d/particle/animator/texture-animation.ts b/cocos2d/core/3d/particle/animator/texture-animation.ts new file mode 100644 index 00000000000..b5a24242847 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/texture-animation.ts @@ -0,0 +1,225 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import Enum from '../../../platform/CCEnum'; +import { lerp, pseudoRandom, repeat } from '../../../value-types'; +import CurveRange from './curve-range'; + +// tslint:disable: max-line-length +const TEXTURE_ANIMATION_RAND_OFFSET = 90794; + +/** + * 粒子贴图动画类型 + * @enum textureAnimationModule.Mode + */ +const Mode = Enum({ + /** + * 网格类型 + */ + Grid: 0, + + /** + * 精灵类型(暂未支持) + */ + //Sprites: 1, +}); + +/** + * 贴图动画的播放方式 + * @enum textureAnimationModule.Animation + */ +const Animation = Enum({ + /** + * 播放贴图中的所有帧 + */ + WholeSheet: 0, + + /** + * 播放贴图中的其中一行动画 + */ + SingleRow: 1, +}); + +/** + * !#en The texture animation module of 3d particle. + * !#zh 3D 粒子的贴图动画模块 + * @class TextureAnimationModule + */ +@ccclass('cc.TextureAnimationModule') +export default class TextureAnimationModule { + + @property + _enable = false; + + /** + * !#en The enable of TextureAnimationModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + get enable () { + return this._enable; + } + + set enable (val) { + this._enable = val; + this.ps._assembler._updateMaterialParams(); + } + + @property + _mode = Mode.Grid; + + /** + * !#en Set the type of particle map animation (only supports Grid mode for the time being) + * !#zh 设定粒子贴图动画的类型(暂只支持 Grid 模式。 + * @property {Mode} mode + */ + @property({ + type: Mode, + }) + get mode () { + return this._mode; + } + + set mode (val) { + if (val !== Mode.Grid) { + console.error('particle texture animation\'s sprites is not supported!'); + return; + } + } + + /** + * !#en Animation frames in X direction. + * !#zh X 方向动画帧数。 + * @property {Number} numTilesX + */ + @property + numTilesX = 0; + + /** + * !#en Animation frames in Y direction. + * !#zh Y 方向动画帧数。 + * @property {Number} numTilesY + */ + @property + numTilesY = 0; + + /** + * !#en The way of the animation plays. + * !#zh 动画播放方式。 + * @property {Animation} animation + */ + @property({ + type: Animation, + }) + animation = Animation.WholeSheet; + + /** + * !#en Randomly select a line from the animated map to generate the animation.
+     * This option only takes effect when the animation playback mode is SingleRow. + * !#zh 随机从动画贴图中选择一行以生成动画。
+ * 此选项仅在动画播放方式为 SingleRow 时生效。 + * @property {Boolean} randomRow + */ + @property + randomRow = false; + + /** + * !#en Select specific lines from the animation map to generate the animation.
+     * This option is only available when the animation playback mode is SingleRow and randomRow is disabled. + * !#zh 从动画贴图中选择特定行以生成动画。
+ * 此选项仅在动画播放方式为 SingleRow 时且禁用 randomRow 时可用。 + * @property {Number} rowIndex + */ + @property + rowIndex = 0; + + /** + * !#en Frame and time curve of animation playback in one cycle. + * !#zh 一个周期内动画播放的帧与时间变化曲线。 + * @property {CurveRange} frameOverTime + */ + @property({ + type: CurveRange, + }) + frameOverTime = new CurveRange(); + + /** + * !#en Play from which frames, the time is the life cycle of the entire particle system. + * !#zh 从第几帧开始播放,时间为整个粒子系统的生命周期。 + * @property {CurveRange} startFrame + */ + @property({ + type: CurveRange, + }) + startFrame = new CurveRange(); + + /** + * !#en Number of playback loops in a life cycle. + * !#zh 一个生命周期内播放循环的次数。 + * @property {Number} cycleCount + */ + @property + cycleCount = 0; + + _flipU = 0; + + @property + get flipU () { + return this._flipU; + } + + set flipU (val) { + console.error('particle texture animation\'s flipU is not supported!'); + } + + _flipV = 0; + + @property + get flipV () { + return this._flipV; + } + + set flipV (val) { + console.error('particle texture animation\'s flipV is not supported!'); + } + + _uvChannelMask = -1; + + @property + get uvChannelMask () { + return this._uvChannelMask; + } + + set uvChannelMask (val) { + console.error('particle texture animation\'s uvChannelMask is not supported!'); + } + + ps = null; + + onInit (ps) { + this.ps = ps; + } + + init (p) { + p.startRow = Math.floor(Math.random() * this.numTilesY); + } + + animate (p) { + const normalizedTime = 1 - p.remainingLifetime / p.startLifetime; + const startFrame = this.startFrame.evaluate(normalizedTime, pseudoRandom(p.randomSeed + TEXTURE_ANIMATION_RAND_OFFSET)) / (this.numTilesX * this.numTilesY); + if (this.animation === Animation.WholeSheet) { + p.frameIndex = repeat(this.cycleCount * (this.frameOverTime.evaluate(normalizedTime, pseudoRandom(p.randomSeed + TEXTURE_ANIMATION_RAND_OFFSET)) + startFrame), 1); + } else if (this.animation === Animation.SingleRow) { + const rowLength = 1 / this.numTilesY; + if (this.randomRow) { + const f = repeat(this.cycleCount * (this.frameOverTime.evaluate(normalizedTime, pseudoRandom(p.randomSeed + TEXTURE_ANIMATION_RAND_OFFSET)) + startFrame), 1); + const from = p.startRow * rowLength; + const to = from + rowLength; + p.frameIndex = lerp(from, to, f); + } else { + const from = this.rowIndex * rowLength; + const to = from + rowLength; + p.frameIndex = lerp(from, to, repeat(this.cycleCount * (this.frameOverTime.evaluate(normalizedTime, pseudoRandom(p.randomSeed + TEXTURE_ANIMATION_RAND_OFFSET)) + startFrame), 1)); + } + } + } +} diff --git a/cocos2d/core/3d/particle/animator/velocity-overtime.ts b/cocos2d/core/3d/particle/animator/velocity-overtime.ts new file mode 100644 index 00000000000..949de6f8eb4 --- /dev/null +++ b/cocos2d/core/3d/particle/animator/velocity-overtime.ts @@ -0,0 +1,106 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { pseudoRandom, Quat, Vec3 } from '../../../value-types'; +import { Space } from '../enum'; +import { calculateTransform } from '../particle-general-function'; +import CurveRange from './curve-range'; + +// tslint:disable: max-line-length +const VELOCITY_OVERTIME_RAND_OFFSET = 197866; + +const _temp_v3 = cc.v3(); + +/** + * !#en The velocity module of 3d particle. + * !#zh 3D 粒子的速度模块 + * @class VelocityOvertimeModule + */ +@ccclass('cc.VelocityOvertimeModule') +export default class VelocityOvertimeModule { + + /** + * !#en The enable of VelocityOvertimeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + /** + * !#en Coordinate system used in speed calculation. + * !#zh 速度计算时采用的坐标系。 + * @property {Space} space + */ + @property({ + type: Space, + }) + space = Space.Local; + + /** + * !#en Velocity component in X axis direction + * !#zh X 轴方向上的速度分量。 + * @property {CurveRange} x + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + x = new CurveRange(); + + /** + * !#en Velocity component in Y axis direction + * !#zh Y 轴方向上的速度分量。 + * @property {CurveRange} y + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + y = new CurveRange(); + + /** + * !#en Velocity component in Z axis direction + * !#zh Z 轴方向上的速度分量。 + * @property {CurveRange} z + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + z = new CurveRange(); + + /** + * !#en Speed correction factor (only supports CPU particles). + * !#zh 速度修正系数(只支持 CPU 粒子)。 + * @property {CurveRange} speedModifier + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + speedModifier = new CurveRange(); + + rotation = null; + needTransform = false; + + constructor () { + this.rotation = new Quat(); + this.speedModifier.constant = 1; + this.needTransform = false; + } + + update (space, worldTransform) { + this.needTransform = calculateTransform(space, this.space, worldTransform, this.rotation); + } + + animate (p) { + const normalizedTime = 1 - p.remainingLifetime / p.startLifetime; + const vel = Vec3.set(_temp_v3, this.x.evaluate(normalizedTime, pseudoRandom(p.randomSeed + VELOCITY_OVERTIME_RAND_OFFSET)), this.y.evaluate(normalizedTime, pseudoRandom(p.randomSeed + VELOCITY_OVERTIME_RAND_OFFSET)), this.z.evaluate(normalizedTime, pseudoRandom(p.randomSeed + VELOCITY_OVERTIME_RAND_OFFSET))); + if (this.needTransform) { + Vec3.transformQuat(vel, vel, this.rotation); + } + Vec3.add(p.animatedVelocity, p.animatedVelocity, vel); + Vec3.add(p.ultimateVelocity, p.velocity, p.animatedVelocity); + Vec3.scale(p.ultimateVelocity, p.ultimateVelocity, this.speedModifier.evaluate(1 - p.remainingLifetime / p.startLifetime, pseudoRandom(p.randomSeed + VELOCITY_OVERTIME_RAND_OFFSET))); + } + +} diff --git a/cocos2d/core/3d/particle/burst.ts b/cocos2d/core/3d/particle/burst.ts new file mode 100644 index 00000000000..a51d02ba9dc --- /dev/null +++ b/cocos2d/core/3d/particle/burst.ts @@ -0,0 +1,111 @@ +import { ccclass, property } from '../../platform/CCClassDecorator'; +import { repeat } from '../../value-types'; +import CurveRange from './animator/curve-range'; + +/** + * !#en The burst of 3d particle. + * !#zh 3D 粒子发射时的爆发个数 + * @class Burst + */ +@ccclass('cc.Burst') +export default class Burst { + + @property + _time = 0; + + /** + * !#en Time between the start of the particle system and the trigger of this Brust + * !#zh 粒子系统开始运行到触发此次 Brust 的时间 + * @property {Number} time + */ + @property + get time () { + return this._time; + } + + set time (val) { + this._time = val; + this._curTime = val; + } + + /** + * !#en Minimum number of emitted particles + * !#zh 发射粒子的最小数量 + * @property {Number} minCount + */ + @property + minCount = 30; + + /** + * !#en Maximum number of emitted particles + * !#zh 发射粒子的最大数量 + * @property {Number} maxCount + */ + @property + maxCount = 30; + + @property + _repeatCount = 1; + + /** + * !#en The number of times Burst was triggered. + * !#zh Burst 的触发次数 + * @property {Number} repeatCount + */ + @property + get repeatCount () { + return this._repeatCount; + } + + set repeatCount (val) { + this._repeatCount = val; + this._remainingCount = val; + } + + /** + * !#en Interval of each trigger + * !#zh 每次触发的间隔时间 + * @property {Number} repeatInterval + */ + @property + repeatInterval = 1; + + /** + * !#en Number of particles emitted + * !#zh 发射的粒子的数量 + * @property {CurveRange} count + */ + @property({ + type: CurveRange, + }) + count = new CurveRange(); + + _remainingCount = 0; + _curTime = 0; + + constructor () { + this._remainingCount = 0; + this._curTime = 0.0; + } + + update (psys, dt) { + if (this._remainingCount === 0) { + this._remainingCount = this._repeatCount; + this._curTime = this._time; + } + if (this._remainingCount > 0) { + let preFrameTime = repeat(psys._time - psys.startDelay.evaluate(0, 1), psys.duration) - dt; + preFrameTime = (preFrameTime > 0.0) ? preFrameTime : 0.0; + const curFrameTime = repeat(psys.time - psys.startDelay.evaluate(0, 1), psys.duration); + if (this._curTime >= preFrameTime && this._curTime < curFrameTime) { + psys.emit(this.count.evaluate(this._curTime / psys.duration, 1), dt - (curFrameTime - this._curTime)); + this._curTime += this.repeatInterval; + --this._remainingCount; + } + } + } + + getMaxCount (psys) { + return this.count.getMax() * Math.min(Math.ceil(psys.duration / this.repeatInterval), this.repeatCount); + } +} diff --git a/cocos2d/core/3d/particle/curve.ts b/cocos2d/core/3d/particle/curve.ts new file mode 100644 index 00000000000..ed13d9c92a9 --- /dev/null +++ b/cocos2d/core/3d/particle/curve.ts @@ -0,0 +1,313 @@ +import Enum from '../../platform/CCEnum'; +import { clamp, inverseLerp, pingPong, repeat } from '../../value-types'; +import { ccclass , property} from '../../platform/CCClassDecorator'; + +const LOOK_FORWARD = 3; + +/** + * !#en The wrap mode + * !#zh 循环模式 + * @static + * @enum AnimationCurve.WrapMode + */ +const WrapMode = Enum({ + /** + * !#en Default + * !#zh 默认模式 + * @property Default + * @readonly + * @type {Number} + */ + Default: 0, + /** + * !#en Once Mode + * !#zh Once 模式 + * @property Once + * @readonly + * @type {Number} + */ + Once: 1, + /** + * !#en Loop Mode + * !#zh Loop 模式 + * @property Loop + * @readonly + * @type {Number} + */ + Loop: 2, + /** + * !#en PingPong Mode + * !#zh PingPong 模式 + * @property PingPong + * @readonly + * @type {Number} + */ + PingPong: 3, + /** + * !#en ClampForever Mode + * !#zh ClampForever 模式 + * @property ClampForever + * @readonly + * @type {Number} + */ + ClampForever: 4, +}); + +@ccclass('cc.Keyframe') +export class Keyframe { + /** + * !#en Time. + * !#zh 时间。 + * @property {Number} time + */ + @property + time = 0; + /** + * !#en Key value. + * !#zh 关键值。 + * @property {Number} value + */ + @property + value = 0; + /** + * !#en In tangent value. + * !#zh 左切值。 + * @property {Number} inTangent + */ + @property + inTangent = 0; + /** + * !#en Out tangent value. + * !#zh 右切值。 + * @property {Number} outTangent + */ + @property + outTangent = 0; + + constructor (time, value, inTangent, outTangent) { + this.time = time || 0; + this.value = value || 0; + this.inTangent = inTangent || 0; + this.outTangent = outTangent || 0; + } +} + +export class OptimizedKey { + index = 0; + time = 0; + endTime = 0; + coefficient = null; + + constructor () { + this.index = -1; + this.time = 0; + this.endTime = 0; + this.coefficient = new Float32Array(4); + } + + evaluate (T) { + const t = T - this.time; + return evalOptCurve(t, this.coefficient); + } +} + +export function evalOptCurve (t, coefs) { + return (t * (t * (t * coefs[0] + coefs[1]) + coefs[2])) + coefs[3]; +} + +const defaultKFStart = new Keyframe(0, 1, 0, 0); +const defaultKFEnd = new Keyframe(1, 1, 0, 0); + + +/** + * !#en The animation curve of 3d particle. + * !#zh 3D 粒子动画曲线 + * @class AnimationCurve + */ +@ccclass('cc.AnimationCurve') +export class AnimationCurve { + /** + * !#en Array of key value. + * !#zh 关键值列表。 + * @property {[Keyframe]} keyFrames + */ + @property({ + type: [Keyframe], + }) + keyFrames = new Array(); + /** + * !#en Pre-wrap mode. + * !#zh 前置循环模式。 + * @property {WrapMode} preWrapMode + */ + @property({ + type: cc.Enum(WrapMode), + visible: false, + }) + preWrapMode = WrapMode.Loop; + /** + * !#en Post-wrap mode. + * !#zh 后置循环模式。 + * @property {WrapMode} postWrapMode + */ + @property({ + type: cc.Enum(WrapMode), + visible: false, + }) + postWrapMode = WrapMode.Loop; + + cachedKey = null; + + constructor (keyFrames = null) { + if (keyFrames) { + this.keyFrames = keyFrames + } else { + this.keyFrames.push(defaultKFStart); + this.keyFrames.push(defaultKFEnd); + } + this.cachedKey = new OptimizedKey(); + } + + addKey (keyFrame) { + if (this.keyFrames == null) { + this.keyFrames = []; + } + this.keyFrames.push(keyFrame); + } + + // cubic Hermite spline + evaluate_slow (time) { + let wrappedTime = time; + const wrapMode = time < 0 ? this.preWrapMode : this.postWrapMode; + const startTime = this.keyFrames[0].time; + const endTime = this.keyFrames[this.keyFrames.length - 1].time; + switch (wrapMode) { + case WrapMode.Loop: + wrappedTime = repeat(time - startTime, endTime - startTime) + startTime; + break; + case WrapMode.PingPong: + wrappedTime = pingPong(time - startTime, endTime - startTime) + startTime; + break; + case WrapMode.ClampForever: + wrappedTime = clamp(time, startTime, endTime); + break; + } + let preKFIndex = 0; + if (wrappedTime > this.keyFrames[0].time) { + if (wrappedTime >= this.keyFrames[this.keyFrames.length - 1].time) { + preKFIndex = this.keyFrames.length - 2; + } + else { + for (let i = 0; i < this.keyFrames.length - 1; i++) { + if (wrappedTime >= this.keyFrames[0].time && wrappedTime <= this.keyFrames[i + 1].time) { + preKFIndex = i; + break; + } + } + } + } + const keyframe0 = this.keyFrames[preKFIndex]; + const keyframe1 = this.keyFrames[preKFIndex + 1]; + + const t = inverseLerp(keyframe0.time, keyframe1.time, wrappedTime); + const dt = keyframe1.time - keyframe0.time; + + const m0 = keyframe0.outTangent * dt; + const m1 = keyframe1.inTangent * dt; + + const t2 = t * t; + const t3 = t2 * t; + + const a = 2 * t3 - 3 * t2 + 1; + const b = t3 - 2 * t2 + t; + const c = t3 - t2; + const d = -2 * t3 + 3 * t2; + + return a * keyframe0.value + b * m0 + c * m1 + d * keyframe1.value; + } + + evaluate (time) { + let wrappedTime = time; + const wrapMode = time < 0 ? this.preWrapMode : this.postWrapMode; + const startTime = this.keyFrames[0].time; + const endTime = this.keyFrames[this.keyFrames.length - 1].time; + switch (wrapMode) { + case WrapMode.Loop: + wrappedTime = repeat(time - startTime, endTime - startTime) + startTime; + break; + case WrapMode.PingPong: + wrappedTime = pingPong(time - startTime, endTime - startTime) + startTime; + break; + case WrapMode.ClampForever: + wrappedTime = clamp(time, startTime, endTime); + break; + } + if (wrappedTime >= this.cachedKey.time && wrappedTime < this.cachedKey.endTime) { + return this.cachedKey.evaluate(wrappedTime); + } else { + const leftIndex = this.findIndex(this.cachedKey, wrappedTime); + let rightIndex = leftIndex + 1; + if (rightIndex === this.keyFrames.length) { + rightIndex -= 1; + } + this.calcOptimizedKey(this.cachedKey, leftIndex, rightIndex); + return this.cachedKey.evaluate(wrappedTime); + } + } + + calcOptimizedKey (optKey, leftIndex, rightIndex) { + const lhs = this.keyFrames[leftIndex]; + const rhs = this.keyFrames[rightIndex]; + optKey.index = leftIndex; + optKey.time = lhs.time; + optKey.endTime = rhs.time; + + const dx = rhs.time - lhs.time; + const dy = rhs.value - lhs.value; + const length = 1 / (dx * dx); + const d1 = lhs.outTangent * dx; + const d2 = rhs.inTangent * dx; + + optKey.coefficient[0] = (d1 + d2 - dy - dy) * length / dx; + optKey.coefficient[1] = (dy + dy + dy - d1 - d1 - d2) * length; + optKey.coefficient[2] = lhs.outTangent; + optKey.coefficient[3] = lhs.value; + } + + findIndex (optKey, t) { + const cachedIndex = optKey.index; + if (cachedIndex !== -1) { + const cachedTime = this.keyFrames[cachedIndex].time; + if (t > cachedTime) { + for (let i = 0; i < LOOK_FORWARD; i++) { + const currIndex = cachedIndex + i; + if (currIndex + 1 < this.keyFrames.length && this.keyFrames[currIndex + 1].time > t) { + return currIndex; + } + } + } else { + for (let i = 0; i < LOOK_FORWARD; i++) { + const currIndex = cachedIndex - i; + if (currIndex >= 0 && this.keyFrames[currIndex - 1].time <= t) { + return currIndex - 1; + } + } + } + } + let left = 0; + let right = this.keyFrames.length; + let mid = Math.floor((left + right) / 2); + while (right - left > 1) { + if (this.keyFrames[mid].time >= t) { + right = mid; + } else { + left = mid + 1; + } + mid = Math.floor((left + right) / 2); + } + return left; + } +} + +cc.Keyframe = Keyframe; +cc.AnimationCurve = AnimationCurve; \ No newline at end of file diff --git a/cocos2d/core/3d/particle/emitter/shape-module.ts b/cocos2d/core/3d/particle/emitter/shape-module.ts new file mode 100644 index 00000000000..5ecafb78c84 --- /dev/null +++ b/cocos2d/core/3d/particle/emitter/shape-module.ts @@ -0,0 +1,456 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { clamp, Mat4, pingPong, Quat, random, randomRange, repeat, toDegree, toRadian, Vec2, Vec3 } from '../../../value-types'; +import CurveRange from '../animator/curve-range'; +import { fixedAngleUnitVector2, particleEmitZAxis, randomPointBetweenCircleAtFixedAngle, randomPointBetweenSphere, randomPointInCube, randomSign, randomSortArray, randomUnitVector } from '../particle-general-function'; +import { ShapeType, EmitLocation, ArcMode } from '../enum'; + +// tslint:disable: max-line-length +const _intermediVec = new Vec3(0, 0, 0); +const _intermediArr = new Array(); +const _unitBoxExtent = new Vec3(0.5, 0.5, 0.5); + +/** + * !#en The shape module of 3d particle. + * !#zh 3D 粒子的发射形状模块 + * @class ShapeModule + */ +@ccclass('cc.ShapeModule') +export default class ShapeModule { + + /** + * !#en The enable of shapeModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + enable = false; + + @property + _shapeType = ShapeType.Cone; + + /** + * !#en Particle emitter type. + * !#zh 粒子发射器类型。 + * @property {ShapeType} shapeType + */ + @property({ + type: ShapeType, + }) + public get shapeType () { + return this._shapeType; + } + + public set shapeType (val) { + this._shapeType = val; + switch (this._shapeType) { + case ShapeType.Box: + if (this.emitFrom === EmitLocation.Base) { + this.emitFrom = EmitLocation.Volume; + } + break; + case ShapeType.Cone: + if (this.emitFrom === EmitLocation.Edge) { + this.emitFrom = EmitLocation.Base; + } + break; + case ShapeType.Sphere: + case ShapeType.Hemisphere: + if (this.emitFrom === EmitLocation.Base || this.emitFrom === EmitLocation.Edge) { + this.emitFrom = EmitLocation.Volume; + } + break; + } + } + + /** + * !#en The emission site of the particle. + * !#zh 粒子从发射器哪个部位发射。 + * @property {EmitLocation} emitFrom + */ + @property({ + type: EmitLocation, + }) + emitFrom = EmitLocation.Volume; + + /** + * !#en Particle emitter radius. + * !#zh 粒子发射器半径。 + * @property {Number} radius + */ + @property + radius = 1; + + /** + * !#en Particle emitter emission position (not valid for Box type emitters): + * - 0 means emitted from the surface; +     * - 1 means launch from the center; +     * - 0 ~ 1 indicates emission from the center to the surface. + * !#zh 粒子发射器发射位置(对 Box 类型的发射器无效): + * - 0 表示从表面发射; + * - 1 表示从中心发射; + * - 0 ~ 1 之间表示在中心到表面之间发射。 + * @property {Number} radiusThickness + */ + @property + radiusThickness = 1; + + @property + _angle = toRadian(25); + + /** + * !#en The angle between the axis of the cone and the generatrix + * Determines the opening and closing of the cone launcher + * !#zh 圆锥的轴与母线的夹角。 + * 决定圆锥发射器的开合程度。 + * @property {Number} angle + */ + @property + get angle () { + return Math.round(toDegree(this._angle) * 100) / 100; + } + + set angle (val) { + this._angle = toRadian(val); + } + + @property + _arc = toRadian(360); + + /** + * !#en Particle emitters emit in a fan-shaped range. + * !#zh 粒子发射器在一个扇形范围内发射。 + * @property {Number} arc + */ + @property + get arc () { + return toDegree(this._arc); + } + + set arc (val) { + this._arc = toRadian(val); + } + + /** + * !#en How particles are emitted in the sector range. + * !#zh 粒子在扇形范围内的发射方式。 + * @property {ArcMode} arcMode + */ + @property({ + type: ArcMode, + }) + arcMode = ArcMode.Random; + + /** + * !#en Controls the discrete intervals around the arcs where particles might be generated. + * !#zh 控制可能产生粒子的弧周围的离散间隔。 + * @property {Number} arcSpread + */ + @property + arcSpread = 0; + + /** + * !#en The speed at which particles are emitted around the circumference. + * !#zh 粒子沿圆周发射的速度。 + * @property {CurveRange} arcSpeed + */ + @property({ + type: CurveRange, + }) + arcSpeed = new CurveRange(); + + /** + * !#en Axis length from top of cone to bottom of cone . +     * Determines the height of the cone emitter. + * !#zh 圆锥顶部截面距离底部的轴长。 + * 决定圆锥发射器的高度。 + * @property {Number} length + */ + @property + length = 5; + + /** + * !#en Particle emitter emission location (for box-type particle emitters). + * !#zh 粒子发射器发射位置(针对 Box 类型的粒子发射器。 + * @property {Vec3} boxThickness + */ + @property + boxThickness = new Vec3(0, 0, 0); + + @property + _position = new Vec3(0, 0, 0); + + /** + * !#en Particle Emitter Position + * !#zh 粒子发射器位置。 + * @property {Vec3} position + */ + @property + get position () { + return this._position; + } + set position (val) { + this._position = val; + this.constructMat(); + } + + @property + _rotation = new Vec3(0, 0, 0); + + /** + * !#en Particle emitter rotation angle. + * !#zh 粒子发射器旋转角度。 + * @property {Vec3} rotation + */ + @property + get rotation () { + return this._rotation; + } + set rotation (val) { + this._rotation = val; + this.constructMat(); + } + + @property + _scale = new Vec3(1, 1, 1); + + /** + * !#en Particle emitter scaling + * !#zh 粒子发射器缩放比例。 + * @property {Vec3} scale + */ + @property + get scale () { + return this._scale; + } + set scale (val) { + this._scale = val; + this.constructMat(); + } + + /** + * !#en The direction of particle movement is determined based on the initial direction of the particles. + * !#zh 根据粒子的初始方向决定粒子的移动方向。 + * @property {Boolean} alignToDirection + */ + @property + alignToDirection = false; + + /** + * !#en Set particle generation direction randomly. + * !#zh 粒子生成方向随机设定。 + * @property {Number} randomDirectionAmount + */ + @property + randomDirectionAmount = 0; + + /** + * !#en Interpolation between the current emission direction and the direction from the current position to the center of the node. + * !#zh 表示当前发射方向与当前位置到结点中心连线方向的插值。 + * @property {Number} sphericalDirectionAmount + */ + @property + sphericalDirectionAmount = 0; + + /** + * !#en Set the particle generation position randomly (setting this value to a value other than 0 will cause the particle generation position to exceed the generator size range) + * !#zh 粒子生成位置随机设定(设定此值为非 0 会使粒子生成位置超出生成器大小范围)。 + */ + @property + randomPositionAmount = 0; + + mat = null; + Quat = null; + particleSystem = null; + lastTime = null; + totalAngle = null; + + constructor () { + this.mat = new Mat4(); + this.quat = new Quat(); + this.particleSystem = null; + this.lastTime = 0; + this.totalAngle = 0; + } + + onInit (ps) { + this.particleSystem = ps; + this.constructMat(); + this.lastTime = this.particleSystem._time; + } + + constructMat () { + Quat.fromEuler(this.quat, this._rotation.x, this._rotation.y, this._rotation.z); + Mat4.fromRTS(this.mat, this.quat, this._position, this._scale); + } + + emit (p) { + switch (this.shapeType) { + case ShapeType.Box: + boxEmit(this.emitFrom, this.boxThickness, p.position, p.velocity); + break; + case ShapeType.Circle: + circleEmit(this.radius, this.radiusThickness, this.generateArcAngle(), p.position, p.velocity); + break; + case ShapeType.Cone: + coneEmit(this.emitFrom, this.radius, this.radiusThickness, this.generateArcAngle(), this._angle, this.length, p.position, p.velocity); + break; + case ShapeType.Sphere: + sphereEmit(this.emitFrom, this.radius, this.radiusThickness, p.position, p.velocity); + break; + case ShapeType.Hemisphere: + hemisphereEmit(this.emitFrom, this.radius, this.radiusThickness, p.position, p.velocity); + break; + default: + console.warn(this.shapeType + ' shapeType is not supported by ShapeModule.'); + } + if (this.randomPositionAmount > 0) { + p.position.x += randomRange(-this.randomPositionAmount, this.randomPositionAmount); + p.position.y += randomRange(-this.randomPositionAmount, this.randomPositionAmount); + p.position.z += randomRange(-this.randomPositionAmount, this.randomPositionAmount); + } + Vec3.transformQuat(p.velocity, p.velocity, this.quat); + Vec3.transformMat4(p.position, p.position, this.mat); + if (this.sphericalDirectionAmount > 0) { + const sphericalVel = Vec3.normalize(_intermediVec, p.position); + Vec3.lerp(p.velocity, p.velocity, sphericalVel, this.sphericalDirectionAmount); + } + this.lastTime = this.particleSystem._time; + } + + generateArcAngle () { + if (this.arcMode === ArcMode.Random) { + return randomRange(0, this._arc); + } + let angle = this.totalAngle + 2 * Math.PI * this.arcSpeed.evaluate(this.particleSystem._time, 1) * (this.particleSystem._time - this.lastTime); + this.totalAngle = angle; + if (this.arcSpread !== 0) { + angle = Math.floor(angle / (this._arc * this.arcSpread)) * this._arc * this.arcSpread; + } + switch (this.arcMode) { + case ArcMode.Loop: + return repeat(angle, this._arc); + case ArcMode.PingPong: + return pingPong(angle, this._arc); + } + } +} + +function sphereEmit (emitFrom, radius, radiusThickness, pos, dir) { + switch (emitFrom) { + case EmitLocation.Volume: + randomPointBetweenSphere(pos, radius * (1 - radiusThickness), radius); + Vec3.copy(dir, pos); + Vec3.normalize(dir, dir); + break; + case EmitLocation.Shell: + randomUnitVector(pos); + Vec3.scale(pos, pos, radius); + Vec3.copy(dir, pos); + break; + default: + console.warn(emitFrom + ' is not supported for sphere emitter.'); + } +} + +function hemisphereEmit (emitFrom, radius, radiusThickness, pos, dir) { + switch (emitFrom) { + case EmitLocation.Volume: + randomPointBetweenSphere(pos, radius * (1 - radiusThickness), radius); + if (pos.z > 0) { + pos.z *= -1; + } + Vec3.copy(dir, pos); + Vec3.normalize(dir, dir); + break; + case EmitLocation.Shell: + randomUnitVector(pos); + Vec3.scale(pos, pos, radius); + if (pos.z < 0) { + pos.z *= -1; + } + Vec3.copy(dir, pos); + break; + default: + console.warn(emitFrom + ' is not supported for hemisphere emitter.'); + } +} + +function coneEmit (emitFrom, radius, radiusThickness, theta, angle, length, pos, dir) { + switch (emitFrom) { + case EmitLocation.Base: + randomPointBetweenCircleAtFixedAngle(pos, radius * (1 - radiusThickness), radius, theta); + Vec2.scale(dir, pos, Math.sin(angle)); + dir.z = -Math.cos(angle) * radius; + Vec3.normalize(dir, dir); + pos.z = 0; + break; + case EmitLocation.Shell: + fixedAngleUnitVector2(pos, theta); + Vec2.scale(dir, pos, Math.sin(angle)); + dir.z = -Math.cos(angle); + Vec3.normalize(dir, dir); + Vec2.scale(pos, pos, radius); + pos.z = 0; + break; + case EmitLocation.Volume: + randomPointBetweenCircleAtFixedAngle(pos, radius * (1 - radiusThickness), radius, theta); + Vec2.scale(dir, pos, Math.sin(angle)); + dir.z = -Math.cos(angle) * radius; + Vec3.normalize(dir, dir); + pos.z = 0; + Vec3.add(pos, pos, Vec3.scale(_intermediVec, dir, length * random() / -dir.z)); + break; + default: + console.warn(emitFrom + ' is not supported for cone emitter.'); + } +} + +function boxEmit (emitFrom, boxThickness, pos, dir) { + switch (emitFrom) { + case EmitLocation.Volume: + randomPointInCube(pos, _unitBoxExtent); + // randomPointBetweenCube(pos, Vec3.multiply(_intermediVec, _unitBoxExtent, boxThickness), _unitBoxExtent); + break; + case EmitLocation.Shell: + _intermediArr.splice(0, _intermediArr.length); + _intermediArr.push(randomRange(-0.5, 0.5)); + _intermediArr.push(randomRange(-0.5, 0.5)); + _intermediArr.push(randomSign() * 0.5); + randomSortArray(_intermediArr); + applyBoxThickness(_intermediArr, boxThickness); + Vec3.set(pos, _intermediArr[0], _intermediArr[1], _intermediArr[2]); + break; + case EmitLocation.Edge: + _intermediArr.splice(0, _intermediArr.length); + _intermediArr.push(randomRange(-0.5, 0.5)); + _intermediArr.push(randomSign() * 0.5); + _intermediArr.push(randomSign() * 0.5); + randomSortArray(_intermediArr); + applyBoxThickness(_intermediArr, boxThickness); + Vec3.set(pos, _intermediArr[0], _intermediArr[1], _intermediArr[2]); + break; + default: + console.warn(emitFrom + ' is not supported for box emitter.'); + } + Vec3.copy(dir, particleEmitZAxis); +} + +function circleEmit (radius, radiusThickness, theta, pos, dir) { + randomPointBetweenCircleAtFixedAngle(pos, radius * (1 - radiusThickness), radius, theta); + Vec3.normalize(dir, pos); +} + +function applyBoxThickness (pos, thickness) { + if (thickness.x > 0) { + pos[0] += 0.5 * randomRange(-thickness.x, thickness.x); + pos[0] = clamp(pos[0], -0.5, 0.5); + } + if (thickness.y > 0) { + pos[1] += 0.5 * randomRange(-thickness.y, thickness.y); + pos[1] = clamp(pos[1], -0.5, 0.5); + } + if (thickness.z > 0) { + pos[2] += 0.5 * randomRange(-thickness.z, thickness.z); + pos[2] = clamp(pos[2], -0.5, 0.5); + } +} diff --git a/cocos2d/core/3d/particle/enum.ts b/cocos2d/core/3d/particle/enum.ts new file mode 100644 index 00000000000..2414eae0435 --- /dev/null +++ b/cocos2d/core/3d/particle/enum.ts @@ -0,0 +1,166 @@ +import Enum from '../../platform/CCEnum'; + +/** + * @enum ParticleSystem3DAssembler.Space + */ +export const Space = Enum({ + World: 0, + Local: 1, + Custom: 2, +}); + +/** + * 粒子的生成模式 + * @enum ParticleSystem3DAssembler.RenderMode + */ +export const RenderMode = Enum({ + + /** + * 粒子始终面向摄像机 + */ + Billboard: 0, + + /** + * 粒子始终面向摄像机但会根据参数进行拉伸 + */ + StrecthedBillboard: 1, + + /** + * 粒子始终与 XZ 平面平行 + */ + HorizontalBillboard: 2, + + /** + * 粒子始终与 Y 轴平行且朝向摄像机 + */ + VerticalBillboard: 3, + + /** + * 粒子保持模型本身状态 + */ + Mesh: 4, +}); + +/** + * 粒子发射器类型 + * @enum shapeModule.ShapeType + */ +export const ShapeType = Enum({ + /** + * 立方体类型粒子发射器 + * @property {Number} Box + */ + Box: 0, + + /** + * 圆形粒子发射器 + * @property {Number} Circle + */ + Circle: 1, + + /** + * 圆锥体粒子发射器 + * @property {Number} Cone + */ + Cone: 2, + + /** + * 球体粒子发射器 + * @property {Number} Sphere + */ + Sphere: 3, + + /** + * 半球体粒子发射器 + * @property {Number} Hemisphere + */ + Hemisphere: 4, +}); + +/** + * 粒子从发射器的哪个部位发射 + * @enum shapeModule.EmitLocation + */ +export const EmitLocation = Enum({ + /** + * 基础位置发射(仅对 Circle 类型及 Cone 类型的粒子发射器适用) + * @property {Number} Base + */ + Base: 0, + + /** + * 边框位置发射(仅对 Box 类型及 Circle 类型的粒子发射器适用) + * @property {Number} Edge + */ + Edge: 1, + + /** + * 表面位置发射(对所有类型的粒子发射器都适用) + * @property {Number} Shell + */ + Shell: 2, + + /** + * 内部位置发射(对所有类型的粒子发射器都适用) + * @property {Number} Volume + */ + Volume: 3, +}); + +/** + * 粒子在扇形区域的发射方式 + * @enum shapeModule.ArcMode + */ +export const ArcMode = Enum({ + /** + * 随机位置发射 + * @property {Number} Random + */ + Random: 0, + + /** + * 沿某一方向循环发射,每次循环方向相同 + * @property {Number} Loop + */ + Loop: 1, + + /** + * 循环发射,每次循环方向相反 + * @property {Number} PingPong + */ + PingPong: 2, +}); + +/** + * 选择如何为粒子系统生成轨迹 + * @enum trailModule.TrailMode + */ +export const TrailMode = Enum({ + /** + * 粒子模式 + * 创建一种效果,其中每个粒子在其路径中留下固定的轨迹 + */ + Particles: 0, + + /** + * 带模式 + * 根据其生命周期创建连接每个粒子的轨迹带 + */ + Ribbon: 1, +}); + +/** + * 纹理填充模式 + * @enum trailModule.TextureMode + */ +export const TextureMode = Enum({ + /** + * 拉伸填充纹理 + */ + Stretch: 0, + + /** + * 重复填充纹理 + */ + Repeat: 1, +}); diff --git a/cocos2d/core/3d/particle/particle-general-function.ts b/cocos2d/core/3d/particle/particle-general-function.ts new file mode 100644 index 00000000000..ef3fe9b38ed --- /dev/null +++ b/cocos2d/core/3d/particle/particle-general-function.ts @@ -0,0 +1,111 @@ +import { Mat4, Quat, random, randomRange, randomRangeInt, Vec2, Vec3 } from '../../value-types'; +import { sign } from '../../value-types/utils'; +import { Space } from './enum'; + +export const particleEmitZAxis = new Vec3(0, 0, -1); + +export function calculateTransform (systemSpace, moduleSpace, worldTransform, outQuat) { + if (moduleSpace !== systemSpace) { + if (systemSpace === Space.World) { + Mat4.getRotation(outQuat, worldTransform); + } + else { + Mat4.invert(worldTransform, worldTransform); + Mat4.getRotation(outQuat, worldTransform); + } + return true; + } + else { + Quat.set(outQuat, 0, 0, 0, 1); + return false; + } +} + +export function fixedAngleUnitVector2 (out, theta) { + Vec2.set(out, Math.cos(theta), Math.sin(theta)); +} + +export function randomUnitVector2 (out) { + const a = randomRange(0, 2 * Math.PI); + const x = Math.cos(a); + const y = Math.sin(a); + Vec2.set(out, x, y); +} + +export function randomUnitVector (out) { + const z = randomRange(-1, 1); + const a = randomRange(0, 2 * Math.PI); + const r = Math.sqrt(1 - z * z); + const x = r * Math.cos(a); + const y = r * Math.sin(a); + Vec3.set(out, x, y, z); +} + +export function randomPointInUnitSphere (out) { + randomUnitVector(out); + Vec3.scale(out, out, random()); +} + +export function randomPointBetweenSphere (out, minRadius, maxRadius) { + randomUnitVector(out); + Vec3.scale(out, out, minRadius + (maxRadius - minRadius) * random()); +} + +export function randomPointInUnitCircle (out) { + randomUnitVector2(out); + out.z = 0; + Vec3.scale(out, out, random()); +} + +export function randomPointBetweenCircle (out, minRadius, maxRadius) { + randomUnitVector2(out); + out.z = 0; + Vec3.scale(out, out, minRadius + (maxRadius - minRadius) * random()); +} + +export function randomPointBetweenCircleAtFixedAngle (out, minRadius, maxRadius, theta) { + fixedAngleUnitVector2(out, theta); + out.z = 0; + Vec3.scale(out, out, minRadius + (maxRadius - minRadius) * random()); +} + +export function randomPointInCube (out, extents) { + Vec3.set(out, + randomRange(-extents.x, extents.x), + randomRange(-extents.y, extents.y), + randomRange(-extents.z, extents.z)); +} + +export function randomPointBetweenCube (out, minBox, maxBox) { + const subscript = ['x', 'y', 'z']; + const edge = randomRangeInt(0, 3); + for (let i = 0; i < 3; i++) { + if (i === edge) { + out[subscript[i]] = randomRange(-maxBox[subscript[i]], maxBox[subscript[i]]); + continue; + } + const x = random() * 2 - 1; + if (x < 0) { + out[subscript[i]] = -minBox[subscript[i]] + x * (maxBox[subscript[i]] - minBox[subscript[i]]); + } + else { + out[subscript[i]] = minBox[subscript[i]] + x * (maxBox[subscript[i]] - minBox[subscript[i]]); + } + } +} + +// Fisher–Yates shuffle +export function randomSortArray (arr) { + for (let i = 0; i < arr.length; i++) { + const transpose = i + randomRangeInt(0, arr.length - i); + const val = arr[transpose]; + arr[transpose] = arr[i]; + arr[i] = val; + } +} + +export function randomSign () { + let sgn = randomRange(-1, 1); + sgn === 0 ? sgn++ : sgn; + return sign(sgn); +} diff --git a/cocos2d/core/3d/particle/particle-system-3d.ts b/cocos2d/core/3d/particle/particle-system-3d.ts new file mode 100644 index 00000000000..b25af38bc3e --- /dev/null +++ b/cocos2d/core/3d/particle/particle-system-3d.ts @@ -0,0 +1,963 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { Mat4, pseudoRandom, Quat, randomRangeInt, Vec2, Vec3 } from '../../value-types'; +import { INT_MAX } from '../../value-types/utils'; +import Material from '../../assets/material/CCMaterial'; +import ColorOverLifetimeModule from './animator/color-overtime'; +import CurveRange, { Mode }from './animator/curve-range'; +import ForceOvertimeModule from './animator/force-overtime'; +import GradientRange from './animator/gradient-range'; +import LimitVelocityOvertimeModule from './animator/limit-velocity-overtime'; +import RotationOvertimeModule from './animator/rotation-overtime'; +import SizeOvertimeModule from './animator/size-overtime'; +import TextureAnimationModule from './animator/texture-animation'; +import VelocityOvertimeModule from './animator/velocity-overtime'; +import Burst from './burst'; +import ShapeModule from './emitter/shape-module'; +import { RenderMode, Space } from './enum'; +import { particleEmitZAxis } from './particle-general-function'; +import TrailModule from './renderer/trail'; +import Mesh from '../../mesh/CCMesh'; + +const { ccclass, menu, property, executeInEditMode, executionOrder} = require('../../platform/CCClassDecorator') +const RenderComponent = require('../../components/CCRenderComponent'); + +const _world_mat = new Mat4(); +const _module_props = CC_EDITOR && [ + "_colorOverLifetimeModule", + "_shapeModule", + "_sizeOvertimeModule", + "_velocityOvertimeModule", + "_forceOvertimeModule", + "_limitVelocityOvertimeModule", + "_rotationOvertimeModule", + "_textureAnimationModule", + "_trailModule" +] + +/** + * !#en The ParticleSystem3D Component. + * !#zh 3D 粒子组件 + * @class ParticleSystem3D + * @extends RenderComponent + */ +@ccclass('cc.ParticleSystem3D') +@menu('i18n:MAIN_MENU.component.renderers/ParticleSystem3D') +@executionOrder(99) +@executeInEditMode +export default class ParticleSystem3D extends RenderComponent { + /** + * !#en The run time of particle. + * !#zh 粒子系统运行时间 + * @property {Number} duration + */ + @property + duration = 5.0; + + @property + _capacity = 100; + /** + * !#en The maximum number of particles that a particle system can generate. + * !#zh 粒子系统能生成的最大粒子数量 + * @property {Number} capacity + */ + @property + get capacity () { + return this._capacity; + } + + set capacity (val) { + this._capacity = val; + if (this._assembler) { + this._assembler.setCapacity(this._capacity); + } + } + + /** + * !#en Whether the particle system loops. + * !#zh 粒子系统是否循环播放 + * @property {Boolean} loop + */ + @property + loop = true; + + /** + * !#en Whether the particles start playing automatically after loaded. + * !#zh 粒子系统加载后是否自动开始播放 + * @property {Boolean} playOnAwake + */ + @property({ + animatable: false + }) + playOnAwake = true; + + @property + _prewarm = false; + /** + * !#en When selected, the particle system will start playing after one round has been played (only effective when loop is enabled). + * !#zh 选中之后,粒子系统会以已播放完一轮之后的状态开始播放(仅当循环播放启用时有效) + * @property {Boolean} prewarm + */ + @property({ + animatable: false + }) + get prewarm () { + return this._prewarm; + } + + set prewarm (val) { + if (val === true && this.loop === false) { + // console.warn('prewarm only works if loop is also enabled.'); + } + this._prewarm = val; + } + + @property + _simulationSpace = Space.Local; + /** + * !#en The coordinate system in which the particle system is located.
+ * World coordinates (does not change when the position of other objects changes)
+ * Local coordinates (moving as the position of the parent node changes)
+ * Custom coordinates (moving with the position of a custom node) + * !#zh 选择粒子系统所在的坐标系
+ * 世界坐标(不随其他物体位置改变而变换)
+ * 局部坐标(跟随父节点位置改变而移动)
+ * 自定坐标(跟随自定义节点的位置改变而移动) + * @property {Space} simulationSpace + */ + @property({ + type: Space, + animatable: false + }) + get simulationSpace () { + return this._simulationSpace; + } + + set simulationSpace (val) { + if (val !== this._simulationSpace) { + this._simulationSpace = val; + this._assembler._updateMaterialParams(); + this._assembler._updateTrailMaterial(); + } + } + + /** + * !#en Controlling the update speed of the entire particle system. + * !#zh 控制整个粒子系统的更新速度。 + * @property {Number} simulationSpeed + */ + @property + simulationSpeed = 1.0; + + /** + * !#en Delay particle emission time after particle system starts running. + * !#zh 粒子系统开始运行后,延迟粒子发射的时间。 + * @property {CurveRange} startDelay + */ + @property({ + type: CurveRange, + }) + startDelay = new CurveRange(); + + /** + * !#en Particle life cycle。 + * !#zh 粒子生命周期。 + * @property {CurveRange} startLifetime + */ + @property({ + type: CurveRange, + }) + startLifetime = new CurveRange(); + + /** + * !#en Particle initial color + * !#zh 粒子初始颜色 + * @property {GradientRange} startColor + */ + @property({ + type: GradientRange, + }) + startColor = new GradientRange(); + + /** + * !#en Particle scale space + * !#zh 缩放空间 + * @property {Space} scaleSpace + */ + @property({ + type: Space, + }) + scaleSpace = Space.Local; + + /** + * !#en Initial particle size + * !#zh 粒子初始大小 + * @property {CurveRange} startSize + */ + @property({ + type: CurveRange, + }) + startSize = new CurveRange(); + + /** + * !#en Initial particle speed + * !#zh 粒子初始速度 + * @property {CurveRange} startSpeed + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + startSpeed = new CurveRange(); + + /** + * !#en Particle initial rotation angle + * !#zh 粒子初始旋转角度 + * @property {CurveRange} startRotation + */ + @property({ + type: CurveRange, + range: [-1, 1], + radian: true, + }) + startRotation = new CurveRange(); + + /** + * !#en Gravity coefficient of particles affected by gravity + * !#zh 粒子受重力影响的重力系数 + * @property {CurveRange} gravityModifier + */ + @property({ + type: CurveRange, + range: [-1, 1], + }) + gravityModifier = new CurveRange(); + + // emission module + /** + * !#en Particles emitted per second + * !#zh 每秒发射的粒子数 + * @property {CurveRange} rateOverTime + */ + @property({ + type: CurveRange, + }) + rateOverTime = new CurveRange(); + + /** + * !#en Number of particles emitted per unit distance moved + * !#zh 每移动单位距离发射的粒子数 + * @property {CurveRange} rateOverDistance + */ + @property({ + type: CurveRange, + }) + rateOverDistance = new CurveRange(); + + /** + * !#en The number of Brusts that emit a specified number of particles at a specified time + * !#zh 设定在指定时间发射指定数量的粒子的 Brust 的数量 + * @property {[Burst]} bursts + */ + @property({ + type: [Burst], + animatable: false + }) + bursts = new Array(); + + @property({ + type: [Material], + displayName: 'Materials', + visible: false, + override: true, + }) + get materials () { + // if we don't create an array copy, the editor will modify the original array directly. + return this._materials; + } + + set materials (val) { + this._materials = val; + this._activateMaterial(); + } + + @property + // shpae module + _shapeModule = new ShapeModule(); + /** + * !#en Particle emitter module + * !#zh 粒子发射器模块 + * @property {ShapeModule} shapeModule + */ + @property({ + type: ShapeModule, + animatable: false + }) + get shapeModule () { + return this._shapeModule; + } + set shapeModule (val) { + this._shapeModule = val; + this._shapeModule.onInit(this); + } + + @property + // color over lifetime module + _colorOverLifetimeModule = new ColorOverLifetimeModule(); + /** + * !#en Color control module + * !#zh 颜色控制模块 + * @property {ColorOverLifetimeModule} colorOverLifetimeModule + */ + @property({ + type: ColorOverLifetimeModule, + animatable: false + }) + get colorOverLifetimeModule () { + return this._colorOverLifetimeModule; + } + set colorOverLifetimeModule (val) { + this._colorOverLifetimeModule = val; + } + + @property + // size over lifetime module + _sizeOvertimeModule = new SizeOvertimeModule(); + /** + * !#en Particle size module + * !#zh 粒子大小模块 + * @property {SizeOvertimeModule} sizeOvertimeModule + */ + @property({ + type: SizeOvertimeModule, + animatable: false + }) + get sizeOvertimeModule () { + return this._sizeOvertimeModule; + } + set sizeOvertimeModule (val) { + this._sizeOvertimeModule = val; + } + + @property + _velocityOvertimeModule = new VelocityOvertimeModule(); + /** + * !#en Particle speed module + * !#zh 粒子速度模块 + * @property {VelocityOvertimeModule} velocityOvertimeModule + */ + @property({ + type: VelocityOvertimeModule, + animatable: false + }) + get velocityOvertimeModule () { + return this._velocityOvertimeModule; + } + + set velocityOvertimeModule (val) { + this._velocityOvertimeModule = val; + } + + @property + _forceOvertimeModule = new ForceOvertimeModule(); + /** + * !#en Particle acceleration module + * !#zh 粒子加速度模块 + * @property {ForceOvertimeModule} forceOvertimeModule + */ + @property({ + type: ForceOvertimeModule, + animatable: false + }) + get forceOvertimeModule () { + return this._forceOvertimeModule; + } + set forceOvertimeModule (val) { + this._forceOvertimeModule = val; + } + + @property + _limitVelocityOvertimeModule = new LimitVelocityOvertimeModule(); + /** + * !#en Particle limit speed module (only CPU particles are supported) + * !#zh 粒子限制速度模块(只支持 CPU 粒子) + * @property {LimitVelocityOvertimeModule} limitVelocityOvertimeModule + */ + @property({ + type: LimitVelocityOvertimeModule, + animatable: false + }) + get limitVelocityOvertimeModule () { + return this._limitVelocityOvertimeModule; + } + set limitVelocityOvertimeModule (val) { + this._limitVelocityOvertimeModule = val; + } + + @property + _rotationOvertimeModule = new RotationOvertimeModule(); + /** + * !#en Particle rotation module + * !#zh 粒子旋转模块 + * @property {RotationOvertimeModule} rotationOvertimeModule + */ + @property({ + type: RotationOvertimeModule, + animatable: false + }) + get rotationOvertimeModule () { + return this._rotationOvertimeModule; + } + set rotationOvertimeModule (val) { + this._rotationOvertimeModule = val; + } + + @property + _textureAnimationModule = new TextureAnimationModule(); + /** + * !#en Texture Animation Module + * !#zh 贴图动画模块 + * @property {TextureAnimationModule} textureAnimationModule + */ + @property({ + type: TextureAnimationModule, + animatable: false + }) + get textureAnimationModule () { + return this._textureAnimationModule; + } + set textureAnimationModule (val) { + this._textureAnimationModule = val; + this._textureAnimationModule.onInit(this); + } + + @property + _trailModule = new TrailModule(); + /** + * !#en Particle Trajectory Module + * !#zh 粒子轨迹模块 + * @property {TrailModule} trailModule + */ + @property({ + type: TrailModule, + animatable: false + }) + get trailModule () { + return this._trailModule; + } + set trailModule (val) { + this._trailModule = val; + this._trailModule.onInit(this); + } + + @property + _renderMode = RenderMode.Billboard; + + /** + * !#en Particle generation mode + * !#zh 设定粒子生成模式 + * @property {RenderMode} renderMode + */ + @property({ + type: RenderMode, + animatable: false + }) + get renderMode () { + return this._renderMode; + } + + set renderMode (val) { + if (this._renderMode === val) { + return; + } + this._renderMode = val; + this._assembler._setVertexAttrib(); + this._assembler._updateModel(); + this._assembler._updateMaterialParams(); + } + + @property + _velocityScale = 1; + + /** + * !#en When the particle generation mode is StrecthedBillboard, in the direction of movement of the particles is stretched by velocity magnitude + * !#zh 在粒子生成方式为 StrecthedBillboard 时,对粒子在运动方向上按速度大小进行拉伸 + * @property {Number} velocityScale + */ + @property({ + animatable: false + }) + get velocityScale () { + return this._velocityScale; + } + + set velocityScale (val) { + this._velocityScale = val; + this._assembler._updateMaterialParams(); + } + + @property + _lengthScale = 1; + /** + * !#en When the particle generation method is StrecthedBillboard, the particles are stretched according to the particle size in the direction of motion + * !#zh 在粒子生成方式为 StrecthedBillboard 时,对粒子在运动方向上按粒子大小进行拉伸 + * @property {Number} lengthScale + */ + @property({ + animatable: false + }) + get lengthScale () { + return this._lengthScale; + } + + set lengthScale (val) { + this._lengthScale = val; + this._assembler._updateMaterialParams(); + } + + @property + _mesh = null; + + /** + * !#en Particle model + * !#zh 粒子模型 + * @property {Mesh} mesh + */ + @property({ + type: Mesh, + animatable: false + }) + get mesh () { + return this._mesh; + } + + set mesh (val) { + this._mesh = val; + this._assembler._updateModel(); + } + + /** + * !#en Particle material + * !#zh 粒子材质 + * @property {Material} particleMaterial + */ + @property({ + type: Material, + animatable: false + }) + get particleMaterial () { + return this.getMaterial(0); + } + + set particleMaterial (val) { + this.setMaterial(0, val); + this._onMaterialModified(0, val); + } + + /** + * !#en Particle trail material + * !#zh 粒子轨迹材质 + * @property {Material} trailMaterial + */ + @property({ + type: Material, + animatable: false + }) + get trailMaterial () { + return this.getMaterial(1); + } + + set trailMaterial (val) { + this.setMaterial(1, val); + this._onMaterialModified(1, val); + } + + _isPlaying; + _isPaused; + _isStopped; + _isEmitting; + _time; // playback position in seconds. + _emitRateTimeCounter; + _emitRateDistanceCounter; + _oldWPos; + _curWPos; + _customData1; + _customData2; + _subEmitters; // array of { emitter: ParticleSystem3D, type: 'birth', 'collision' or 'death'} + + constructor () { + super(); + + this.rateOverTime.constant = 10; + this.startLifetime.constant = 5; + this.startSize.constant = 1; + this.startSpeed.constant = 5; + + // internal status + this._isPlaying = false; + this._isPaused = false; + this._isStopped = true; + this._isEmitting = false; + + this._time = 0.0; // playback position in seconds. + this._emitRateTimeCounter = 0.0; + this._emitRateDistanceCounter = 0.0; + this._oldWPos = new Vec3(0, 0, 0); + this._curWPos = new Vec3(0, 0, 0); + + this._customData1 = new Vec2(0, 0); + this._customData2 = new Vec2(0, 0); + + this._subEmitters = []; // array of { emitter: ParticleSystemComponent, type: 'birth', 'collision' or 'death'} + } + + onLoad () { + this._assembler.onInit(this); + this.shapeModule.onInit(this); + this.trailModule.onInit(this); + this.textureAnimationModule.onInit(this); + + this._resetPosition(); + + // this._system.add(this); + } + + _onMaterialModified (index, material) { + this._assembler._onMaterialModified(index, material); + } + + _onRebuildPSO (index, material) { + this._assembler._onRebuildPSO(index, material); + } + + // TODO: fastforward current particle system by simulating particles over given period of time, then pause it. + // simulate(time, withChildren, restart, fixedTimeStep) { + + // } + + /** + * !#en Playing particle effects + * !#zh 播放粒子效果 + * @method play + */ + play () { + if (this._isPaused) { + this._isPaused = false; + } + if (this._isStopped) { + this._isStopped = false; + } + + this._isPlaying = true; + this._isEmitting = true; + + this._resetPosition(); + + // prewarm + if (this._prewarm) { + this._prewarmSystem(); + } + } + + /** + * !#en Pause particle effect + * !#zh 暂停播放粒子效果 + * @method pause + */ + pause () { + if (this._isStopped) { + console.warn('pause(): particle system is already stopped.'); + return; + } + if (this._isPlaying) { + this._isPlaying = false; + } + + this._isPaused = true; + } + + /** + * !#en Stop particle effect + * !#zh 停止播放粒子效果 + * @method stop + */ + stop () { + if (this._isPlaying || this._isPaused) { + this.clear(); + } + if (this._isPlaying) { + this._isPlaying = false; + } + if (this._isPaused) { + this._isPaused = false; + } + + this._time = 0.0; + this._emitRateTimeCounter = 0.0; + this._emitRateDistanceCounter = 0.0; + + this._isStopped = true; + } + + // remove all particles from current particle system. + /** + * !#en Remove all particle effect + * !#zh 将所有粒子从粒子系统中清除 + * @method clear + */ + clear () { + if (this.enabledInHierarchy) { + this._assembler.clear(); + this.trailModule.clear(); + } + } + + getParticleCount () { + return this._assembler.getParticleCount(); + } + + setCustomData1 (x, y) { + Vec2.set(this._customData1, x, y); + } + + setCustomData2 (x, y) { + Vec2.set(this._customData2, x, y); + } + + onDestroy () { + // this._system.remove(this); + this._assembler.onDestroy(); + this.trailModule.destroy(); + } + + onEnable () { + super.onEnable(); + if (this.playOnAwake) { + this.play(); + } + this._assembler.onEnable(); + this.trailModule.onEnable(); + } + + onDisable () { + super.onDisable(); + this._assembler.onDisable(); + this.trailModule.onDisable(); + } + + update (dt) { + const scaledDeltaTime = dt * this.simulationSpeed; + if (this._isPlaying) { + this._time += scaledDeltaTime; + + // excute emission + this._emit(scaledDeltaTime); + + // simulation, update particles. + if (this._assembler._updateParticles(scaledDeltaTime) === 0 && !this._isEmitting) { + this.stop(); + } + + // update render data + this._assembler.updateParticleBuffer(); + + // update trail + if (this.trailModule.enable) { + this.trailModule.updateTrailBuffer(); + } + } + } + + emit (count, dt) { + + if (this._simulationSpace === Space.World) { + this.node.getWorldMatrix(_world_mat); + } + + for (let i = 0; i < count; ++i) { + const particle = this._assembler._getFreeParticle(); + if (particle === null) { + return; + } + const rand = pseudoRandom(randomRangeInt(0, INT_MAX)); + + if (this.shapeModule.enable) { + this.shapeModule.emit(particle); + } + else { + Vec3.set(particle.position, 0, 0, 0); + Vec3.copy(particle.velocity, particleEmitZAxis); + } + + if (this.textureAnimationModule.enable) { + this.textureAnimationModule.init(particle); + } + + Vec3.scale(particle.velocity, particle.velocity, this.startSpeed.evaluate(this._time / this.duration, rand)); + + switch (this._simulationSpace) { + case Space.Local: + break; + case Space.World: + Vec3.transformMat4(particle.position, particle.position, _world_mat); + const worldRot = new Quat(); + this.node.getWorldRotation(worldRot); + Vec3.transformQuat(particle.velocity, particle.velocity, worldRot); + break; + case Space.Custom: + // TODO: + break; + } + Vec3.copy(particle.ultimateVelocity, particle.velocity); + // apply startRotation. now 2D only. + Vec3.set(particle.rotation, 0, 0, this.startRotation.evaluate(this._time / this.duration, rand)); + + // apply startSize. now 2D only. + Vec3.set(particle.startSize, this.startSize.evaluate(this._time / this.duration, rand), 1, 1); + particle.startSize.y = particle.startSize.x; + Vec3.copy(particle.size, particle.startSize); + + // apply startColor. + particle.startColor.set(this.startColor.evaluate(this._time / this.duration, rand)); + particle.color.set(particle.startColor); + + // apply startLifetime. + particle.startLifetime = this.startLifetime.evaluate(this._time / this.duration, rand) + dt; + particle.remainingLifetime = particle.startLifetime; + + particle.randomSeed = randomRangeInt(0, 233280); + + this._assembler._setNewParticle(particle); + + } // end of particles forLoop. + } + + // initialize particle system as though it had already completed a full cycle. + _prewarmSystem () { + this.startDelay.mode = Mode.Constant; // clear startDelay. + this.startDelay.constant = 0; + const dt = 1.0; // should use varying value? + const cnt = this.duration / dt; + for (let i = 0; i < cnt; ++i) { + this._time += dt; + this._emit(dt); + this._assembler._updateParticles(dt); + } + } + + // internal function + _emit (dt) { + // emit particles. + const startDelay = this.startDelay.evaluate(0, 1); + if (this._time > startDelay) { + if (this._time > (this.duration + startDelay)) { + // this._time = startDelay; // delay will not be applied from the second loop.(Unity) + // this._emitRateTimeCounter = 0.0; + // this._emitRateDistanceCounter = 0.0; + if (!this.loop) { + this._isEmitting = false; + return; + } + } + + // emit by rateOverTime + this._emitRateTimeCounter += this.rateOverTime.evaluate(this._time / this.duration, 1) * dt; + if (this._emitRateTimeCounter > 1 && this._isEmitting) { + const emitNum = Math.floor(this._emitRateTimeCounter); + this._emitRateTimeCounter -= emitNum; + this.emit(emitNum, dt); + } + // emit by rateOverDistance + this.node.getWorldPosition(this._curWPos); + const distance = Vec3.distance(this._curWPos, this._oldWPos); + Vec3.copy(this._oldWPos, this._curWPos); + this._emitRateDistanceCounter += distance * this.rateOverDistance.evaluate(this._time / this.duration, 1); + if (this._emitRateDistanceCounter > 1 && this._isEmitting) { + const emitNum = Math.floor(this._emitRateDistanceCounter); + this._emitRateDistanceCounter -= emitNum; + this.emit(emitNum, dt); + } + + // bursts + for (const burst of this.bursts) { + burst.update(this, dt); + } + } + } + + _activateMaterial () { + + } + + _resetPosition () { + this.node.getWorldPosition(this._oldWPos); + Vec3.copy(this._curWPos, this._oldWPos); + } + + addSubEmitter (subEmitter) { + this._subEmitters.push(subEmitter); + } + + removeSubEmitter (idx) { + this._subEmitters.splice(this._subEmitters.indexOf(idx), 1); + } + + addBurst (burst) { + this.bursts.push(burst); + } + + removeBurst (idx) { + this.bursts.splice(this.bursts.indexOf(idx), 1); + } + + _checkBacth () { + + } + + get isPlaying () { + return this._isPlaying; + } + + get isPaused () { + return this._isPaused; + } + + get isStopped () { + return this._isStopped; + } + + get isEmitting () { + return this._isEmitting; + } + + get time () { + return this._time; + } +} + +CC_EDITOR && (ParticleSystem3D.prototype._onBeforeSerialize = function(props){return props.filter(p => !_module_props.includes(p) || this[p].enable);}); + +cc.ParticleSystem3D = ParticleSystem3D; diff --git a/cocos2d/core/3d/particle/particle-utils.ts b/cocos2d/core/3d/particle/particle-utils.ts new file mode 100644 index 00000000000..f8b4002115b --- /dev/null +++ b/cocos2d/core/3d/particle/particle-utils.ts @@ -0,0 +1,50 @@ +import { Pool } from '../../../renderer/memop'; + +export class ParticleUtils { + static particleSystemPool = {}; + static registeredSceneEvent = false; + + /** + * instantiate + */ + static instantiate (prefab) { + if (!this.registeredSceneEvent) { + cc.director.on(cc.Director.EVENT_BEFORE_SCENE_LAUNCH, this.onSceneUnload, this); + this.registeredSceneEvent = true; + } + if (!this.particleSystemPool.has(prefab._uuid)) { + this.particleSystemPool.set(prefab._uuid, new Pool(() => { + return cc.instantiate(prefab); + }, 1)); + } + return this.particleSystemPool.get(prefab._uuid).alloc(); + } + + static destroy (prefab) { + if (this.particleSystemPool.has(prefab._prefab.asset._uuid)) { + this.stop(prefab); + this.particleSystemPool.get(prefab._prefab.asset._uuid).free(prefab); + } + } + + static onSceneUnload () { + for (const p of this.particleSystemPool.values()) { + p.clear((prefab) => { + prefab.destroy(); + }); + } + this.particleSystemPool.clear(); + } + + static play (rootNode) { + for (const ps of rootNode.getComponentsInChildren(cc.ParticleSystem3D)) { + ps.play(); + } + } + + static stop (rootNode) { + for (const ps of rootNode.getComponentsInChildren(cc.ParticleSystem3D)) { + ps.stop(); + } + } +} diff --git a/cocos2d/core/3d/particle/particle.ts b/cocos2d/core/3d/particle/particle.ts new file mode 100644 index 00000000000..b78f674773d --- /dev/null +++ b/cocos2d/core/3d/particle/particle.ts @@ -0,0 +1,43 @@ +import { Vec3, Color } from '../../value-types'; + +export default class Particle { + particleSystem = null; + position = null; + velocity = null; + animatedVelocity = null; + ultimateVelocity = null; + angularVelocity = null; + axisOfRotation = null; + rotation = null; + startSize = null; + size = null; + startColor = null; + color = cc.Color.WHITE; + randomSeed = null; // uint + remainingLifetime = null; + startLifetime = null; + emitAccumulator0 = null; + emitAccumulator1 = null; + frameIndex = null; + + constructor (particleSystem) { + this.particleSystem = particleSystem; + this.position = new Vec3(0, 0, 0); + this.velocity = new Vec3(0, 0, 0); + this.animatedVelocity = new Vec3(0, 0, 0); + this.ultimateVelocity = new Vec3(0, 0, 0); + this.angularVelocity = new Vec3(0, 0, 0); + this.axisOfRotation = new Vec3(0, 0, 0); + this.rotation = new Vec3(0, 0, 0); + this.startSize = new Vec3(0, 0, 0); + this.size = new Vec3(0, 0, 0); + this.startColor = cc.Color.WHITE.clone(); + this.color = cc.Color.WHITE.clone(); + this.randomSeed = 0; // uint + this.remainingLifetime = 0.0; + this.startLifetime = 0.0; + this.emitAccumulator0 = 0.0; + this.emitAccumulator1 = 0.0; + this.frameIndex = 0.0; + } +} diff --git a/cocos2d/core/3d/particle/renderer/particle-batch-model.ts b/cocos2d/core/3d/particle/renderer/particle-batch-model.ts new file mode 100644 index 00000000000..69a527ed818 --- /dev/null +++ b/cocos2d/core/3d/particle/renderer/particle-batch-model.ts @@ -0,0 +1,296 @@ +// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +import gfx from '../../../../renderer/gfx' +import InputAssembler from '../../../../renderer/core/input-assembler' +import { MeshData } from '../../../mesh/mesh-data' + +const renderer = require('../../../renderer'); + +export default class ParticleBatchModel{ + _capacity = 0; + _vertFormat = null; + _vertAttrsFloatCount = 0; + _mesh = null; + _vertCount = 0; + _indexCount = 0; + _material = null; + + constructor () { + this._capacity = 0; + this._vertFormat = null; + this._vertAttrsFloatCount = 0; + this._mesh = null; + + this._subDatas = []; + this._subMeshes = []; + } + + setCapacity (capacity) { + const capChanged = this._capacity !== capacity; + this._capacity = capacity; + if (this._inited && capChanged) { + this._recreateBuffer(); + } + } + + setVertexAttributes (mesh, vfmt) { + if (this._mesh === mesh && this._vertFormat === vfmt) { + return; + } + this._mesh = mesh; + this._vertFormat = vfmt; + this._vertAttrsFloatCount = this._vertFormat._bytes / 4; // number of float + // rebuid + this._createParticleData(); + this._inited = true; + } + + _recreateBuffer () { + this._createParticleData(); + } + + _createParticleData () { + this.destroyIAData(); + this._vertCount = 4; + this._indexCount = 6; + + let vbData = null; + let ibData = null; + let vertSize = this._vertFormat._bytes + + if (this._mesh) { + let subData = this._mesh._subDatas[0]; + + this._vertCount = subData.vData.byteLength / subData.vfm._bytes; + this._indexCount = subData.iData.byteLength / 2; + + vbData = new Float32Array(vertSize * this._capacity * this._vertCount / 4); + ibData = new Uint16Array(this._capacity * this._indexCount); + + let posEle = this._vertFormat.element(gfx.ATTR_TEX_COORD3); + let normalEle = this._vertFormat.element(gfx.ATTR_NORMAL); + let uvEle = this._vertFormat.element(gfx.ATTR_TEX_COORD); + let colorEle = this._vertFormat.element(gfx.ATTR_COLOR1); + + this._mesh.copyAttribute(0, gfx.ATTR_POSITION, vbData.buffer, vertSize, posEle.offset); + this._mesh.copyAttribute(0, gfx.ATTR_NORMAL, vbData.buffer, vertSize, normalEle.offset); + this._mesh.copyAttribute(0, gfx.ATTR_UV0, vbData.buffer, vertSize, uvEle.offset); + + if (!this._mesh.copyAttribute(0, gfx.ATTR_COLOR, vbData.buffer, vertSize, colorEle.offset)) { // copy mesh color to ATTR_COLOR1 + const vb = new Uint32Array(vbData.buffer); + for (var i = 0; i < this._vertCount; ++i) { + vb[i * this._vertAttrsFloatCount + colorEle.offset / 4] = cc.Color.WHITE._val; + } + } + + const vbFloatArray = new Float32Array(vbData.buffer); + for (var i = 1; i < this._capacity; i++) { + vbFloatArray.copyWithin(i * vertSize * this._vertCount / 4, 0, vertSize * this._vertCount / 4); + } + + this._mesh.copyIndices(0, ibData); + // indices + for (var i = 1; i < this._capacity; i++) { + for (var j = 0; j < this._indexCount; j++) { + ibData[i * this._indexCount + j] = ibData[j] + i * this._vertCount; + } + } + } else { + vbData = new Float32Array(vertSize * this._capacity * this._vertCount / 4); + ibData = new Uint16Array(this._capacity * this._indexCount); + + let dst = 0; + for (var i = 0; i < this._capacity; ++i) { + const baseIdx = 4 * i; + ibData[dst++] = baseIdx; + ibData[dst++] = baseIdx + 1; + ibData[dst++] = baseIdx + 2; + ibData[dst++] = baseIdx + 3; + ibData[dst++] = baseIdx + 2; + ibData[dst++] = baseIdx + 1; + } + } + + let meshData = new MeshData(); + meshData.vData = vbData; + meshData.iData = ibData; + meshData.vfm = this._vertFormat; + meshData.vDirty = true; + meshData.iDirty = true; + meshData.enable = true; + this._subDatas[0] = meshData; + + if (CC_JSB && CC_NATIVERENDERER) { + meshData.vDirty = true; + } else { + let vb = new gfx.VertexBuffer( + renderer.device, + this._vertFormat, + gfx.USAGE_DYNAMIC, + vbData + ); + + let ib = new gfx.IndexBuffer( + renderer.device, + gfx.INDEX_FMT_UINT16, + gfx.USAGE_STATIC, + ibData, + ibData.length + ); + + this._subMeshes[0] = new InputAssembler(vb, ib); + } + } + + createTrailData (vfmt, num) { + if (this._subDatas[1]) { + return + } + + let vertSize = vfmt._bytes; + let vBuffer = new ArrayBuffer(vertSize * (num + 1) * 2); + let ibData = new Uint16Array(num * 6); + + let meshData = new MeshData(); + meshData.vData = new Float32Array(vBuffer); + meshData.iData = ibData; + meshData.vfm = vfmt; + meshData.vDirty = true; + meshData.iDirty = true; + meshData.enable = true; + this._subDatas[1] = meshData; + + if (CC_JSB && CC_NATIVERENDERER) { + meshData.vDirty = true; + } else { + let vb = new gfx.VertexBuffer( + renderer.device, + vfmt, + gfx.USAGE_DYNAMIC, + vBuffer + ); + let ib = new gfx.IndexBuffer( + renderer.device, + gfx.INDEX_FMT_UINT16, + gfx.USAGE_DYNAMIC, + ibData, + num * 6 + ); + + this._subMeshes[1] = new InputAssembler(vb, ib); + } + } + + setModelMaterial (mat) { + this._material = mat; + } + + addParticleVertexData (index, pvdata) { + let subData = this._subDatas[0]; + let vData = subData.getVData(); + let uintVData = subData.getVData(Uint32Array); + + if (!this._mesh) { + let offset = index * this._vertAttrsFloatCount; + vData[offset++] = pvdata[0].x; // position + vData[offset++] = pvdata[0].y; + vData[offset++] = pvdata[0].z; + vData[offset++] = pvdata[1].x; // uv + vData[offset++] = pvdata[1].y; + vData[offset++] = pvdata[1].z; // frame idx + vData[offset++] = pvdata[2].x; // size + vData[offset++] = pvdata[2].y; + vData[offset++] = pvdata[2].z; + vData[offset++] = pvdata[3].x; // rotation + vData[offset++] = pvdata[3].y; + vData[offset++] = pvdata[3].z; + uintVData[offset++] = pvdata[4]; // color + if (pvdata[5]) { + vData[offset++] = pvdata[5].x; // velocity + vData[offset++] = pvdata[5].y; + vData[offset++] = pvdata[5].z; + } + } else { + for (let i = 0; i < this._vertCount; i++) { + let offset = (index * this._vertCount + i) * this._vertAttrsFloatCount; + vData[offset++] = pvdata[0].x; // position + vData[offset++] = pvdata[0].y; + vData[offset++] = pvdata[0].z; + offset += 2; + vData[offset++] = pvdata[1].z; // frame idx + vData[offset++] = pvdata[2].x; // size + vData[offset++] = pvdata[2].y; + vData[offset++] = pvdata[2].z; + vData[offset++] = pvdata[3].x; // rotation + vData[offset++] = pvdata[3].y; + vData[offset++] = pvdata[3].z; + uintVData[offset++] = pvdata[4]; // color + } + } + } + + _uploadData () { + let subDatas = this._subDatas; + let subMeshes = this._subMeshes; + for (let i = 0, len = subDatas.length; i < len; i++) { + let subData = subDatas[i]; + let subMesh = subMeshes[i]; + if (subData.vDirty) { + let vBuffer = subMesh._vertexBuffer, vData = subData.vData; + vBuffer.update(0, vData); + subData.vDirty = false; + } + + if (subData.iDirty) { + let iBuffer = subMesh._indexBuffer, iData = subData.iData; + iBuffer.update(0, iData); + subData.iDirty = false; + } + } + } + + updateIA (index, count, vDirty, iDirty) { + if (CC_JSB && CC_NATIVERENDERER) return + + this._subMeshes[index]._count = count; + + let subData = this._subDatas[index]; + subData.vDirty = vDirty; + subData.iDirty = iDirty; + } + + clear () { + let subMesh = this._subMeshes[0]; + if (subMesh) { + subMesh.indexCount = 0; + } + } + + destroy () { + this._subDatas.length = 0; + + let subMeshes = this._subMeshes; + for (let i = 0, len = subMeshes.length; i < len; i++) { + let vb = subMeshes[i]._vertexBuffer; + if (vb) { + vb.destroy(); + } + + let ib = subMeshes[i]._indexBuffer; + if (ib) { + ib.destroy(); + } + } + subMeshes.length = 0; + } + + destroyIAData () { + if (this._subMeshes[0]) { + this._subMeshes[0]._vertexBuffer.destroy(); + this._subMeshes[0]._indexBuffer.destroy(); + this._subMeshes[0] = null; + } + + this._subDatas[0] = null; + } +} diff --git a/cocos2d/core/3d/particle/renderer/particle-system-3d-renderer.ts b/cocos2d/core/3d/particle/renderer/particle-system-3d-renderer.ts new file mode 100644 index 00000000000..c6a436d14a5 --- /dev/null +++ b/cocos2d/core/3d/particle/renderer/particle-system-3d-renderer.ts @@ -0,0 +1,467 @@ +import { Mat4, Vec2, Vec3, Vec4 } from '../../../value-types'; +import gfx from '../../../../renderer/gfx'; +import ParticleBatchModel from './particle-batch-model'; +import MaterialVariant from '../../../assets/material/material-variant'; +import RecyclePool from '../../../../renderer/memop/recycle-pool'; +import { RenderMode, Space } from '../enum'; +import Particle from '../particle'; +import Assembler from '../../../renderer/assembler'; +import ParticleSystem3D from '../particle-system-3d'; + +const { ccclass, property } = require('../../../platform/CCClassDecorator'); + +// tslint:disable: max-line-length +const _tempAttribUV = new Vec3(); +const _tempAttribUV0 = new Vec2(); +const _tempAttribColor = new Vec4(); +const _tempWorldTrans = new Mat4(); + +const _uvs = [ + 0, 0, // bottom-left + 1, 0, // bottom-right + 0, 1, // top-left + 1, 1, // top-right +]; + +const CC_USE_WORLD_SPACE = 'CC_USE_WORLD_SPACE'; +const CC_USE_BILLBOARD = 'CC_USE_BILLBOARD'; +const CC_USE_STRETCHED_BILLBOARD = 'CC_USE_STRETCHED_BILLBOARD'; +const CC_USE_HORIZONTAL_BILLBOARD = 'CC_USE_HORIZONTAL_BILLBOARD'; +const CC_USE_VERTICAL_BILLBOARD = 'CC_USE_VERTICAL_BILLBOARD'; +const CC_USE_MESH = 'CC_USE_MESH'; +//const CC_DRAW_WIRE_FRAME = 'CC_DRAW_WIRE_FRAME'; // + + +var vfmtNormal = new gfx.VertexFormat([ + { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD1, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD2, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, +]); +vfmtNormal.name = 'vfmtNormal'; + +var vfmtStretch = new gfx.VertexFormat([ + { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD1, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD2, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, + { name: gfx.ATTR_COLOR1, type: gfx.ATTR_TYPE_FLOAT32, num: 3} +]); +vfmtStretch.name = 'vfmtStretch'; + +var vfmtMesh = new gfx.VertexFormat([ + { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD1, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD2, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, + { name: gfx.ATTR_TEX_COORD3, type: gfx.ATTR_TYPE_FLOAT32, num: 3 }, + { name: gfx.ATTR_NORMAL, type: gfx.ATTR_TYPE_FLOAT32, num: 3 }, + { name: gfx.ATTR_COLOR1, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true } +]); +vfmtMesh.name = 'vfmtMesh'; + +@ccclass('cc.ParticleSystem3DAssembler') +export default class ParticleSystem3DAssembler extends Assembler { + _defines = null; + _trailDefines = null; + _model = null; + frameTile_velLenScale = null; + attrs = []; + _vertFormat = []; + _particleSystem = null; + _particles = null; + _defaultMat = null; + _isAssetReady = false; + _defaultTrailMat = null; + _customProperties = null; + _node_scale = null; + + constructor () { + super(); + this._model = null; + + this.frameTile_velLenScale = cc.v4(1, 1, 0, 0); + this._node_scale = cc.v4(); + this.attrs = new Array(5); + + this._trailDefines = { + CC_USE_WORLD_SPACE: true, + //CC_DRAW_WIRE_FRAME: true, // + }; + } + + onInit (ps) { + this._particleSystem = ps; + this._particles = new RecyclePool(() => { + return new Particle(this); + }, 16); + this._setVertexAttrib(); + this.onEnable(); + this._updateModel(); + this._updateMaterialParams(); + this._updateTrailMaterial(); + } + + onEnable () { + if (!this._particleSystem) { + return; + } + + if (this._model == null) { + this._model = new ParticleBatchModel(); + } + + if (!this._model.inited) { + this._model.setCapacity(this._particleSystem.capacity); + } + + this._model.enabled = this._particleSystem.enabledInHierarchy; + } + + onDisable () { + if (this._model) { + this._model.enabled = this._particleSystem.enabledInHierarchy; + } + } + + onDestroy () { + this._model = null; + } + + clear () { + this._particles.reset(); + this.updateParticleBuffer(); + } + + _getFreeParticle () { + if (this._particles.length >= this._particleSystem.capacity) { + return null; + } + return this._particles.add(); + } + + _setNewParticle (p) { + + } + + _updateParticles (dt) { + this._particleSystem.node.getWorldMatrix(_tempWorldTrans); + + switch (this._particleSystem.scaleSpace) { + case Space.Local: + this._particleSystem.node.getScale(this._node_scale); + break; + case Space.World: + this._particleSystem.node.getWorldScale(this._node_scale); + break; + } + + let material = this._particleSystem.materials[0]; + let mat = material ? this._particleSystem.particleMaterial : this._defaultMat; + mat.setProperty('scale', this._node_scale); + + if (this._particleSystem.velocityOvertimeModule.enable) { + this._particleSystem.velocityOvertimeModule.update(this._particleSystem._simulationSpace, _tempWorldTrans); + } + if (this._particleSystem.forceOvertimeModule.enable) { + this._particleSystem.forceOvertimeModule.update(this._particleSystem._simulationSpace, _tempWorldTrans); + } + if (this._particleSystem.trailModule.enable) { + this._particleSystem.trailModule.update(); + } + for (let i = 0; i < this._particles.length; ++i) { + const p = this._particles.data[i]; + p.remainingLifetime -= dt; + Vec3.set(p.animatedVelocity, 0, 0, 0); + + if (p.remainingLifetime < 0.0) { + if (this._particleSystem.trailModule.enable) { + this._particleSystem.trailModule.removeParticle(p); + } + this._particles.remove(i); + --i; + continue; + } + + p.velocity.y -= this._particleSystem.gravityModifier.evaluate(1 - p.remainingLifetime / p.startLifetime, p.randomSeed) * 9.8 * dt; // apply gravity. + if (this._particleSystem.sizeOvertimeModule.enable) { + this._particleSystem.sizeOvertimeModule.animate(p); + } + if (this._particleSystem.colorOverLifetimeModule.enable) { + this._particleSystem.colorOverLifetimeModule.animate(p); + } + if (this._particleSystem.forceOvertimeModule.enable) { + this._particleSystem.forceOvertimeModule.animate(p, dt); + } + if (this._particleSystem.velocityOvertimeModule.enable) { + this._particleSystem.velocityOvertimeModule.animate(p); + } else { + Vec3.copy(p.ultimateVelocity, p.velocity); + } + + if (this._particleSystem.limitVelocityOvertimeModule.enable) { + this._particleSystem.limitVelocityOvertimeModule.animate(p); + } + if (this._particleSystem.rotationOvertimeModule.enable) { + this._particleSystem.rotationOvertimeModule.animate(p, dt); + } + if (this._particleSystem.textureAnimationModule.enable) { + this._particleSystem.textureAnimationModule.animate(p); + } + Vec3.scaleAndAdd(p.position, p.position, p.ultimateVelocity, dt); // apply velocity. + if (this._particleSystem.trailModule.enable) { + this._particleSystem.trailModule.animate(p, dt); + } + } + return this._particles.length; + } + + // internal function + updateParticleBuffer () { + // update vertex buffer + let idx = 0; + const uploadVel = this._particleSystem.renderMode === RenderMode.StrecthedBillboard; + for (let i = 0; i < this._particles.length; ++i) { + const p = this._particles.data[i]; + let fi = 0; + if (this._particleSystem.textureAnimationModule.enable) { + fi = p.frameIndex; + } + idx = i * 4; + let attrNum = 0; + if (this._particleSystem.renderMode !== RenderMode.Mesh) { + for (let j = 0; j < 4; ++j) { // four verts per particle. + attrNum = 0; + this.attrs[attrNum++] = p.position; + _tempAttribUV.x = _uvs[2 * j]; + _tempAttribUV.y = _uvs[2 * j + 1]; + _tempAttribUV.z = fi; + this.attrs[attrNum++] = _tempAttribUV; + this.attrs[attrNum++] = p.size; + this.attrs[attrNum++] = p.rotation; + this.attrs[attrNum++] = p.color._val; + + if (uploadVel) { + this.attrs[attrNum++] = p.ultimateVelocity; + } else { + this.attrs[attrNum++] = null; + } + + this._model.addParticleVertexData(idx++, this.attrs); + } + } else { + attrNum = 0; + this.attrs[attrNum++] = p.position; + _tempAttribUV.z = fi; + this.attrs[attrNum++] = _tempAttribUV; + this.attrs[attrNum++] = p.size; + this.attrs[attrNum++] = p.rotation; + this.attrs[attrNum++] = p.color._val; + this._model.addParticleVertexData(i, this.attrs); + } + } + + this.updateIA(0, this._particles.length * this._model._indexCount, true); + } + + updateShaderUniform () { + + } + + updateIA (index, count, vDirty, iDirty) { + if (!this._model) return; + + this._model.updateIA(index, count, vDirty, iDirty); + } + + getParticleCount () { + return this._particles.data.length; + } + + _onMaterialModified (index, material) { + if (index === 0) { + this._updateModel(); + this._updateMaterialParams(); + } else { + this._updateTrailMaterial(); + } + } + + _onRebuildPSO (index, material) { + if (this._model && index === 0) { + this._model.setModelMaterial(material); + } + if (this._particleSystem.trailModule._trailModel && index === 1) { + this._particleSystem.trailModule._trailModel.setModelMaterial(material); + } + } + + _ensureLoadMesh () { + if (this._particleSystem.mesh && !this._particleSystem.mesh.loaded) { + cc.assetManager.postLoadNative(this._particleSystem.mesh); + } + } + + setCapacity (capacity) { + if (!this._model) return; + + this._model.setCapacity(capacity); + } + + _setVertexAttrib () { + switch (this._particleSystem.renderMode) { + case RenderMode.StrecthedBillboard: + this._vertFormat = vfmtStretch; + break; + case RenderMode.Mesh: + this._vertFormat = vfmtMesh; + break; + default: + this._vertFormat = vfmtNormal; + } + } + + _updateMaterialParams () { + if (!this._particleSystem) { + return; + } + let mat = this._particleSystem.materials[0]; + if (mat == null && this._defaultMat == null) { + mat = this._defaultMat = MaterialVariant.createWithBuiltin('3d-particle', this); + } else { + mat = MaterialVariant.create(mat, this._particleSystem); + } + + mat = mat || this._defaultMat; + + if (this._particleSystem._simulationSpace === Space.World) { + mat.define(CC_USE_WORLD_SPACE, true); + } else { + mat.define(CC_USE_WORLD_SPACE, false); + } + + if (this._particleSystem.renderMode === RenderMode.Billboard) { + mat.define(CC_USE_BILLBOARD, true); + mat.define(CC_USE_STRETCHED_BILLBOARD, false); + mat.define(CC_USE_HORIZONTAL_BILLBOARD, false); + mat.define(CC_USE_VERTICAL_BILLBOARD, false); + mat.define(CC_USE_MESH, false); + } else if (this._particleSystem.renderMode === RenderMode.StrecthedBillboard) { + mat.define(CC_USE_BILLBOARD, false); + mat.define(CC_USE_STRETCHED_BILLBOARD, true); + mat.define(CC_USE_HORIZONTAL_BILLBOARD, false); + mat.define(CC_USE_VERTICAL_BILLBOARD, false); + mat.define(CC_USE_MESH, false); + this.frameTile_velLenScale.z = this._particleSystem.velocityScale; + this.frameTile_velLenScale.w = this._particleSystem.lengthScale; + } else if (this._particleSystem.renderMode === RenderMode.HorizontalBillboard) { + mat.define(CC_USE_BILLBOARD, false); + mat.define(CC_USE_STRETCHED_BILLBOARD, false); + mat.define(CC_USE_HORIZONTAL_BILLBOARD, true); + mat.define(CC_USE_VERTICAL_BILLBOARD, false); + mat.define(CC_USE_MESH, false); + } else if (this._particleSystem.renderMode === RenderMode.VerticalBillboard) { + mat.define(CC_USE_BILLBOARD, false); + mat.define(CC_USE_STRETCHED_BILLBOARD, false); + mat.define(CC_USE_HORIZONTAL_BILLBOARD, false); + mat.define(CC_USE_VERTICAL_BILLBOARD, true); + mat.define(CC_USE_MESH, false); + } else if (this._particleSystem.renderMode === RenderMode.Mesh) { + mat.define(CC_USE_BILLBOARD, false); + mat.define(CC_USE_STRETCHED_BILLBOARD, false); + mat.define(CC_USE_HORIZONTAL_BILLBOARD, false); + mat.define(CC_USE_VERTICAL_BILLBOARD, false); + mat.define(CC_USE_MESH, true); + } else { + console.warn(`particle system renderMode ${this._particleSystem.renderMode} not support.`); + } + + if (this._particleSystem.textureAnimationModule.enable) { + Vec2.set(this.frameTile_velLenScale, this._particleSystem.textureAnimationModule.numTilesX, this._particleSystem.textureAnimationModule.numTilesY); + } + + mat.setProperty('frameTile_velLenScale', this.frameTile_velLenScale); + + this._particleSystem.setMaterial(0, mat); + } + + _updateTrailMaterial () { + // Here need to create a material variant through the getter call. + let mat = this._particleSystem.trailMaterial; + if (this._particleSystem.trailModule.enable) { + if (mat === null && this._defaultTrailMat === null) { + this._defaultTrailMat = MaterialVariant.createWithBuiltin('3d-trail', this); + } + + if (mat === null) { + mat = this._defaultTrailMat; + this._particleSystem.trailMaterial = mat; + } + + if (this._particleSystem._simulationSpace === Space.World || this._particleSystem.trailModule.space === Space.World) { + mat.define(CC_USE_WORLD_SPACE, true); + } else { + mat.define(CC_USE_WORLD_SPACE, false); + } + + //mat.define(CC_DRAW_WIRE_FRAME, true); // + this._particleSystem.trailModule._updateMaterial(); + } + } + + _updateTrailEnable (enable) { + if (!this._model) { + return; + } + + let subData = this._model._subDatas[1]; + if (subData) { + subData.enable = enable; + } + } + + _updateModel () { + if (!this._model) { + return; + } + this._model.setVertexAttributes(this._particleSystem.renderMode === RenderMode.Mesh ? this._particleSystem.mesh : null, this._vertFormat); + } + + setVertexAttributes (mesh, vfmt) { + if (!this._model) { + return; + } + this._model.setVertexAttributes(mesh, vfmt); + } + + fillBuffers (comp, renderer) { + if (!this._model) return; + + this._model._uploadData(); + + let submeshes = this._model._subMeshes; + let subDatas = this._model._subDatas; + let materials = comp.materials; + renderer._flush() + for (let i = 0, len = submeshes.length; i < len; i++) { + let ia = submeshes[i]; + let meshData = subDatas[i]; + let material = materials[i]; + + if (meshData.enable) { + renderer.material = material; + renderer.cullingMask = comp.node._cullingMask; + renderer.node = comp.node; + + renderer._flushIA(ia); + } + } + } +} + +Object.assign(ParticleSystem3DAssembler, { uv: _uvs }); + +Assembler.register(ParticleSystem3D, ParticleSystem3DAssembler); diff --git a/cocos2d/core/3d/particle/renderer/trail.ts b/cocos2d/core/3d/particle/renderer/trail.ts new file mode 100644 index 00000000000..c5a468aaae0 --- /dev/null +++ b/cocos2d/core/3d/particle/renderer/trail.ts @@ -0,0 +1,600 @@ +import { ccclass, property } from '../../../platform/CCClassDecorator'; +import { Vec3, toRadian, Color} from '../../../value-types'; +import gfx from '../../../../renderer/gfx'; +import Pool from '../../../../renderer/memop/pool'; +import CurveRange from '../animator/curve-range'; +import GradientRange from '../animator/gradient-range'; +import { Space, TextureMode, TrailMode } from '../enum'; +import MapUtils from '../utils'; + +// tslint:disable: max-line-length +const PRE_TRIANGLE_INDEX = 1; +const NEXT_TRIANGLE_INDEX = 1 << 2; +const DIRECTION_THRESHOLD = Math.cos(toRadian(100)); + +const _temp_trailEle = { position: cc.v3(), velocity: cc.v3() }; +const _temp_quat = cc.quat(); +const _temp_xform = cc.mat4(); +const _temp_Vec3 = cc.v3(); +const _temp_Vec3_1 = cc.v3(); +const _temp_color = cc.color(); + +// var barycentric = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // +// var _bcIdx = 0; + + +class ITrailElement { + position; + lifetime; + width; + velocity; + color; +} + +// the valid element is in [start,end) range.if start equals -1,it represents the array is empty. +class TrailSegment { + start; + end; + trailElements = []; + + constructor (maxTrailElementNum) { + this.start = -1; + this.end = -1; + this.trailElements = []; + while (maxTrailElementNum--) { + this.trailElements.push({ + position: cc.v3(), + lifetime: 0, + width: 0, + velocity: cc.v3(), + direction: 0, + color: cc.color(), + }); + } + } + + getElement (idx) { + if (this.start === -1) { + return null; + } + if (idx < 0) { + idx = (idx + this.trailElements.length) % this.trailElements.length; + } + if (idx >= this.trailElements.length) { + idx %= this.trailElements.length; + } + return this.trailElements[idx]; + } + + addElement () { + if (this.trailElements.length === 0) { + return null; + } + if (this.start === -1) { + this.start = 0; + this.end = 1; + return this.trailElements[0]; + } + if (this.start === this.end) { + this.trailElements.splice(this.end, 0, { + position: cc.v3(), + lifetime: 0, + width: 0, + velocity: cc.v3(), + direction: 0, + color: cc.color(), + }); + this.start++; + this.start %= this.trailElements.length; + } + const newEleLoc = this.end++; + this.end %= this.trailElements.length; + return this.trailElements[newEleLoc]; + } + + iterateElement (target, f, p, dt) { + const end = this.start >= this.end ? this.end + this.trailElements.length : this.end; + for (let i = this.start; i < end; i++) { + if (f(target, this.trailElements[i % this.trailElements.length], p, dt)) { + this.start++; + this.start %= this.trailElements.length; + } + } + if (this.start === end) { + this.start = -1; + this.end = -1; + } + } + + count () { + if (this.start < this.end) { + return this.end - this.start; + } else { + return this.trailElements.length + this.end - this.start; + } + } + + clear () { + this.start = -1; + this.end = -1; + } +} + +/** + * !#en The trail module of 3d particle. + * !#zh 3D 粒子拖尾模块 + * @class TrailModule + */ +@ccclass('cc.TrailModule') +export default class TrailModule { + + @property + _enable = false; + + /** + * !#en The enable of trailModule. + * !#zh 是否启用 + * @property {Boolean} enable + */ + @property + get enable () { + return this._enable; + } + + set enable (val) { + if (val) { + this._createTrailData(); + } + + if (val && !this._enable) { + this._enable = val; + this._particleSystem._assembler._updateTrailMaterial(); + } + + this._enable = val; + this._particleSystem._assembler._updateTrailEnable(this._enable); + } + + /** + * !#en Sets how particles generate trajectories. + * !#zh 设定粒子生成轨迹的方式。 + * @property {TrailMode} mode + */ + @property({ + type: TrailMode, + }) + mode = TrailMode.Particles; + + /** + * !#en Life cycle of trajectory. + * !#zh 轨迹存在的生命周期。 + * @property {CurveRange} lifeTime + */ + @property({ + type: CurveRange, + }) + lifeTime = new CurveRange(); + + @property + _minParticleDistance = 0.1; + + /** + * !#en Minimum spacing between each track particle + * !#zh 每个轨迹粒子之间的最小间距。 + * @property {Number} minParticleDistance + */ + @property + get minParticleDistance () { + return this._minParticleDistance; + } + + set minParticleDistance (val) { + this._minParticleDistance = val; + this._minSquaredDistance = val * val; + } + + @property + _space = Space.World; + + /** + * !#en The coordinate system of trajectories. + * !#zh 轨迹设定时的坐标系。 + * @property {Space} space + */ + @property({ + type: Space, + }) + get space () { + return this._space; + } + + set space (val) { + this._space = val; + if (this._particleSystem) { + this._particleSystem._assembler._updateTrailMaterial(); + } + } + + /** + * !#en Whether the particle itself exists. + * !#zh 粒子本身是否存在。 + * @property {Boolean} existWithParticles + */ + @property + existWithParticles = true; + + /** + * !#en Set the texture fill method + * !#zh 设定纹理填充方式。 + * @property {TextureMode} textureMode + */ + @property({ + type: TextureMode, + }) + textureMode = TextureMode.Stretch; + + /** + * !#en Whether to use particle width + * !#zh 是否使用粒子的宽度。 + * @property {Boolean} widthFromParticle + */ + @property + widthFromParticle = true; + + + /** + * !#en Curves that control track length + * !#zh 控制轨迹长度的曲线。 + * @property {CurveRange} widthRatio + */ + @property({ + type: CurveRange, + }) + widthRatio = new CurveRange(); + + /** + * !#en Whether to use particle color + * !#zh 是否使用粒子的颜色。 + * @property {Boolean} colorFromParticle + */ + @property + colorFromParticle = false; + + /** + * !#en The color of trajectories. + * !#zh 轨迹的颜色。 + * @property {GradientRange} colorOverTrail + */ + @property({ + type: GradientRange, + }) + colorOverTrail = new GradientRange(); + + /** + * !#en Trajectories color over time. + * !#zh 轨迹随时间变化的颜色。 + * @property {GradientRange} colorOvertime + */ + @property({ + type: GradientRange, + }) + colorOvertime = new GradientRange(); + + _particleSystem = null; + _minSquaredDistance = 0; + _vertSize = 0; + _trailNum = 0; + _trailLifetime = 0; + vbOffset = 0; + ibOffset = 0; + _trailSegments = null; + _particleTrail = null; + _ia = null; + _gfxVFmt = null; + _vbF32 = null; + _vbUint32 = null; + _iBuffer = null; + _needTransform = null; + _defaultMat = null; + _material = null; + + constructor () { + this._gfxVFmt = new gfx.VertexFormat([ + { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_TEX_COORD, type: gfx.ATTR_TYPE_FLOAT32, num: 4}, + //{ name: gfx.ATTR_TEX_COORD2, type: gfx.ATTR_TYPE_FLOAT32, num: 3 }, // + { name: gfx.ATTR_TEX_COORD1, type: gfx.ATTR_TYPE_FLOAT32, num: 3}, + { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, + ]); + + this._vertSize = this._gfxVFmt._bytes; + + this._particleTrail = new MapUtils(); // Map(); + } + + onInit (ps) { + this._particleSystem = ps; + this.minParticleDistance = this._minParticleDistance; + let burstCount = 0; + for (const b of ps.bursts) { + burstCount += b.getMaxCount(ps); + } + this.lifeTime.constant = 1; + this._trailNum = Math.ceil(ps.startLifetime.getMax() * this.lifeTime.getMax() * 60 * (ps.rateOverTime.getMax() * ps.duration + burstCount)); + this._trailSegments = new Pool(() => new TrailSegment(10), Math.ceil(ps.rateOverTime.getMax() * ps.duration)); + if (this._enable) { + this.enable = this._enable; + this._updateMaterial(); + } + } + + onEnable () { + } + + onDisable () { + } + + destroy () { + if (this._trailSegments) { + this._trailSegments.clear((obj) => { obj.trailElements.length = 0; }); + this._trailSegments = null; + } + } + + clear () { + if (this.enable) { + const trailIter = this._particleTrail.values(); + let trail = trailIter.next(); + while (!trail.done) { + trail.value.clear(); + trail = trailIter.next(); + } + this._particleTrail.clear(); + this.updateTrailBuffer(); + } + } + + _createTrailData () { + let model = this._particleSystem._assembler._model; + + if (model) { + model.createTrailData(this._gfxVFmt, this._trailNum); + + let subData = model._subDatas[1]; + this._vbF32 = subData.getVData(); + this._vbUint32 = subData.getVData(Uint32Array); + this._iBuffer = subData.iData; + } + } + + _updateMaterial () { + if (this._particleSystem) { + const mat = this._particleSystem.trailMaterial; + if (mat) { + this._material = mat; + } else { + this._material = this._particleSystem._assembler._defaultTrailMat; + } + } + } + + update () { + this._trailLifetime = this.lifeTime.evaluate(this._particleSystem._time, 1); + if (this.space === Space.World && this._particleSystem._simulationSpace === Space.Local) { + this._needTransform = true; + this._particleSystem.node.getWorldMatrix(_temp_xform); + this._particleSystem.node.getWorldRotation(_temp_quat); + } else { + this._needTransform = false; + } + } + + animate (p, scaledDt) { + if (!this._trailSegments) { + return; + } + let trail = this._particleTrail.get(p); + if (!trail) { + trail = this._trailSegments.alloc(); + this._particleTrail.set(p, trail); + return; + } + let lastSeg = trail.getElement(trail.end - 1); + if (this._needTransform) { + Vec3.transformMat4(_temp_Vec3, p.position, _temp_xform); + } else { + Vec3.copy(_temp_Vec3, p.position); + } + if (lastSeg) { + trail.iterateElement(this, this._updateTrailElement, p, scaledDt); + if (Vec3.squaredDistance(lastSeg.position, _temp_Vec3) < this._minSquaredDistance) { + return; + } + } + lastSeg = trail.addElement(); + if (!lastSeg) { + return; + } + Vec3.copy(lastSeg.position, _temp_Vec3); + lastSeg.lifetime = 0; + if (this.widthFromParticle) { + lastSeg.width = p.size.x * this.widthRatio.evaluate(0, 1); + } else { + lastSeg.width = this.widthRatio.evaluate(0, 1); + } + const trailNum = trail.count(); + if (trailNum === 2) { + const lastSecondTrail = trail.getElement(trail.end - 2); + Vec3.subtract(lastSecondTrail.velocity, lastSeg.position, lastSecondTrail.position); + } else if (trailNum > 2) { + const lastSecondTrail = trail.getElement(trail.end - 2); + const lastThirdTrail = trail.getElement(trail.end - 3); + Vec3.subtract(_temp_Vec3, lastThirdTrail.position, lastSecondTrail.position); + Vec3.subtract(_temp_Vec3_1, lastSeg.position, lastSecondTrail.position); + Vec3.subtract(lastSecondTrail.velocity, _temp_Vec3_1, _temp_Vec3); + if (Vec3.equals(cc.Vec3.ZERO, lastSecondTrail.velocity)) { + Vec3.copy(lastSecondTrail.velocity, _temp_Vec3); + } + } + if (this.colorFromParticle) { + lastSeg.color.set(p.color); + } else { + lastSeg.color.set(this.colorOvertime.evaluate(0, 1)); + } + } + + _updateTrailElement (trail, trailEle, p, dt) { + trailEle.lifetime += dt; + if (trail.colorFromParticle) { + trailEle.color.set(p.color); + trailEle.color.multiply(trail.colorOvertime.evaluate(1.0 - p.remainingLifetime / p.startLifetime, 1)); + } else { + trailEle.color.set(trail.colorOvertime.evaluate(1.0 - p.remainingLifetime / p.startLifetime, 1)); + } + if (trail.widthFromParticle) { + trailEle.width = p.size.x * trail.widthRatio.evaluate(trailEle.lifetime / trail._trailLifetime, 1); + } else { + trailEle.width = trail.widthRatio.evaluate(trailEle.lifetime / trail._trailLifetime, 1); + } + return trailEle.lifetime > trail._trailLifetime; + } + + removeParticle (p) { + const trail = this._particleTrail.get(p); + if (trail && this._trailSegments) { + trail.clear(); + this._trailSegments.free(trail); + this._particleTrail.delete(p); + } + } + + updateTrailBuffer () { + this.vbOffset = 0; + this.ibOffset = 0; + + for (const p of this._particleTrail.keys()) { + const trailSeg = this._particleTrail.get(p); + if (trailSeg.start === -1) { + continue; + } + const indexOffset = this.vbOffset * 4 / this._vertSize; + const end = trailSeg.start >= trailSeg.end ? trailSeg.end + trailSeg.trailElements.length : trailSeg.end; + const trailNum = end - trailSeg.start; + // const lastSegRatio = Vec3.distance(trailSeg.getTailElement()!.position, p.position) / this._minParticleDistance; + const textCoordSeg = 1 / (trailNum /*- 1 + lastSegRatio*/); + const startSegEle = trailSeg.trailElements[trailSeg.start]; + this._fillVertexBuffer(startSegEle, this.colorOverTrail.evaluate(1, 1), indexOffset, 1, 0, NEXT_TRIANGLE_INDEX); + for (let i = trailSeg.start + 1; i < end; i++) { + const segEle = trailSeg.trailElements[i % trailSeg.trailElements.length]; + const j = i - trailSeg.start; + this._fillVertexBuffer(segEle, this.colorOverTrail.evaluate(1 - j / trailNum, 1), indexOffset, 1 - j * textCoordSeg, j, PRE_TRIANGLE_INDEX | NEXT_TRIANGLE_INDEX); + } + if (this._needTransform) { + Vec3.transformMat4(_temp_trailEle.position, p.position, _temp_xform); + } else { + Vec3.copy(_temp_trailEle.position, p.position); + } + if (trailNum === 1 || trailNum === 2) { + const lastSecondTrail = trailSeg.getElement(trailSeg.end - 1); + Vec3.subtract(lastSecondTrail.velocity, _temp_trailEle.position, lastSecondTrail.position); + this._vbF32[this.vbOffset - this._vertSize / 4 - 4] = lastSecondTrail.velocity.x; + this._vbF32[this.vbOffset - this._vertSize / 4 - 3] = lastSecondTrail.velocity.y; + this._vbF32[this.vbOffset - this._vertSize / 4 - 2] = lastSecondTrail.velocity.z; + this._vbF32[this.vbOffset - 4] = lastSecondTrail.velocity.x; + this._vbF32[this.vbOffset - 3] = lastSecondTrail.velocity.y; + this._vbF32[this.vbOffset - 2] = lastSecondTrail.velocity.z; + Vec3.subtract(_temp_trailEle.velocity, _temp_trailEle.position, lastSecondTrail.position); + this._checkDirectionReverse(_temp_trailEle, lastSecondTrail); + } else if (trailNum > 2) { + const lastSecondTrail = trailSeg.getElement(trailSeg.end - 1); + const lastThirdTrail = trailSeg.getElement(trailSeg.end - 2); + Vec3.subtract(_temp_Vec3, lastThirdTrail.position, lastSecondTrail.position); + Vec3.subtract(_temp_Vec3_1, _temp_trailEle.position, lastSecondTrail.position); + Vec3.normalize(_temp_Vec3, _temp_Vec3); + Vec3.normalize(_temp_Vec3_1, _temp_Vec3_1); + Vec3.subtract(lastSecondTrail.velocity, _temp_Vec3_1, _temp_Vec3); + Vec3.normalize(lastSecondTrail.velocity, lastSecondTrail.velocity); + this._checkDirectionReverse(lastSecondTrail, lastThirdTrail); + this.vbOffset -= this._vertSize / 4 * 2; + this.ibOffset -= 6; + //_bcIdx = (_bcIdx - 6 + 9) % 9; // + this._fillVertexBuffer(lastSecondTrail, this.colorOverTrail.evaluate(textCoordSeg, 1), indexOffset, textCoordSeg, trailNum - 1, PRE_TRIANGLE_INDEX | NEXT_TRIANGLE_INDEX); + Vec3.subtract(_temp_trailEle.velocity, _temp_trailEle.position, lastSecondTrail.position); + Vec3.normalize(_temp_trailEle.velocity, _temp_trailEle.velocity); + this._checkDirectionReverse(_temp_trailEle, lastSecondTrail); + } + if (this.widthFromParticle) { + _temp_trailEle.width = p.size.x * this.widthRatio.evaluate(0, 1); + } else { + _temp_trailEle.width = this.widthRatio.evaluate(0, 1); + } + _temp_trailEle.color = p.color; + + if (Vec3.equals(_temp_trailEle.velocity, cc.Vec3.ZERO)) { + this.ibOffset -= 3; + } else { + this._fillVertexBuffer(_temp_trailEle, this.colorOverTrail.evaluate(0, 1), indexOffset, 0, trailNum, PRE_TRIANGLE_INDEX); + } + } + this._updateIA(this.ibOffset); + } + + _fillVertexBuffer (trailSeg, colorModifer, indexOffset, xTexCoord, trailEleIdx, indexSet) { + this._vbF32[this.vbOffset++] = trailSeg.position.x; + this._vbF32[this.vbOffset++] = trailSeg.position.y; + this._vbF32[this.vbOffset++] = trailSeg.position.z; + this._vbF32[this.vbOffset++] = 0; + this._vbF32[this.vbOffset++] = trailSeg.width; + this._vbF32[this.vbOffset++] = xTexCoord; + this._vbF32[this.vbOffset++] = 0; + // this._vbF32[this.vbOffset++] = barycentric[_bcIdx++]; // + // this._vbF32[this.vbOffset++] = barycentric[_bcIdx++]; + // this._vbF32[this.vbOffset++] = barycentric[_bcIdx++]; + // _bcIdx %= 9; + this._vbF32[this.vbOffset++] = trailSeg.velocity.x; + this._vbF32[this.vbOffset++] = trailSeg.velocity.y; + this._vbF32[this.vbOffset++] = trailSeg.velocity.z; + _temp_color.set(trailSeg.color); + _temp_color.multiply(colorModifer); + this._vbUint32[this.vbOffset++] = _temp_color._val; + this._vbF32[this.vbOffset++] = trailSeg.position.x; + this._vbF32[this.vbOffset++] = trailSeg.position.y; + this._vbF32[this.vbOffset++] = trailSeg.position.z; + this._vbF32[this.vbOffset++] = 1; + this._vbF32[this.vbOffset++] = trailSeg.width; + this._vbF32[this.vbOffset++] = xTexCoord; + this._vbF32[this.vbOffset++] = 1; + // this._vbF32[this.vbOffset++] = barycentric[_bcIdx++]; // + // this._vbF32[this.vbOffset++] = barycentric[_bcIdx++]; + // this._vbF32[this.vbOffset++] = barycentric[_bcIdx++]; + // _bcIdx %= 9; + this._vbF32[this.vbOffset++] = trailSeg.velocity.x; + this._vbF32[this.vbOffset++] = trailSeg.velocity.y; + this._vbF32[this.vbOffset++] = trailSeg.velocity.z; + this._vbUint32[this.vbOffset++] = _temp_color._val; + if (indexSet & PRE_TRIANGLE_INDEX) { + this._iBuffer[this.ibOffset++] = indexOffset + 2 * trailEleIdx; + this._iBuffer[this.ibOffset++] = indexOffset + 2 * trailEleIdx - 1; + this._iBuffer[this.ibOffset++] = indexOffset + 2 * trailEleIdx + 1; + } + if (indexSet & NEXT_TRIANGLE_INDEX) { + this._iBuffer[this.ibOffset++] = indexOffset + 2 * trailEleIdx; + this._iBuffer[this.ibOffset++] = indexOffset + 2 * trailEleIdx + 1; + this._iBuffer[this.ibOffset++] = indexOffset + 2 * trailEleIdx + 2; + } + } + + _updateIA (count) { + if (this._particleSystem && this._particleSystem._assembler) { + this._particleSystem._assembler.updateIA(1, count, true, true); + } + } + + _checkDirectionReverse (currElement, prevElement) { + if (Vec3.dot(currElement.velocity, prevElement.velocity) < DIRECTION_THRESHOLD) { + currElement.direction = 1 - prevElement.direction; + } else { + currElement.direction = prevElement.direction; + } + } +} diff --git a/cocos2d/core/3d/particle/utils.ts b/cocos2d/core/3d/particle/utils.ts new file mode 100644 index 00000000000..0efa8fe5dd2 --- /dev/null +++ b/cocos2d/core/3d/particle/utils.ts @@ -0,0 +1,120 @@ +// SameValue algorithm +if (!Object.is) { + Object.is = function(x, y) { + if (x === y) { + return x !== 0 || 1 / x === 1 / y; + } else { + return x !== x && y !== y; + } + }; +} + +/** + * !#en + * Helper class for ES5 Map. + * !#zh + * ES5 Map 辅助类。 + * @class MapUtils + */ +export default class MapUtils { + datas = []; + + constructor (data) { + !data && (data = []); + + this.datas = []; + + let that = this; + + data.forEach(function (item) { + if (!that.has(item[0])) { + that.datas.push({ + key: item[0], + value: item[1] + }); + } + }); + } + + size () { + return this.datas.length; + } + + set (key, value) { + this.delete(key); + this.datas.push({ + key: key, + value: value + }); + } + + get (key) { + let value = undefined; + let datas = this.datas; + for (let i = 0, len = datas.length; i < len; i++) { + if (Object.is(key, datas[i].key)) { + value = datas[i].value; + break; + } + } + return value; + } + + has (key) { + let res = false; + let datas = this.datas; + for (let i = 0, len = datas.length; i < len; i++) { + if (Object.is(key, datas[i].key)) { + res = true; + break; + } + } + return res; + } + + clear () { + this.datas.length = 0; + } + + delete (key) { + let res = false; + let datas = this.datas; + for (let i = 0, len = datas.length; i < len; i++) { + if (Object.is(key, datas[i].key)) { + datas.splice(i, 1); + res = true; + break; + } + } + return res; + } + + keys () { + let datas = this.datas; + let keys = []; + for (let i = 0, len = datas.length; i < len; i++) { + keys.push(datas[i].key); + } + + return keys; + } + + values () { + let index = 0; + let datas = this.datas; + return { + next: function () { + if (datas.length === 0 || datas[index] === undefined) { + return { + value: undefined, + done: true + }; + } + return { + value: datas[index++].value, + done: false + }; + } + }; + }; +}; \ No newline at end of file diff --git a/cocos2d/core/3d/physics/cannon/cannon-rigid-body.ts b/cocos2d/core/3d/physics/cannon/cannon-rigid-body.ts new file mode 100644 index 00000000000..e6ee5b83edf --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/cannon-rigid-body.ts @@ -0,0 +1,287 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../external/cannon/cannon'; +import { IRigidBody } from '../spec/I-rigid-body'; +import { CannonSharedBody } from './cannon-shared-body'; +import { CannonWorld } from './cannon-world'; +import { RigidBody3D } from '../framework'; + +const v3_cannon0 = new CANNON.Vec3(); +const v3_cannon1 = new CANNON.Vec3(); +const Vec3 = cc.Vec3; + +/** + * wraped shared body + * dynamic + * kinematic + */ +export class CannonRigidBody implements IRigidBody { + + get isAwake (): boolean { + return this._sharedBody.body.isAwake(); + } + + get isSleepy (): boolean { + return this._sharedBody.body.isSleepy(); + } + + get isSleeping (): boolean { + return this._sharedBody.body.isSleeping(); + } + + set allowSleep (v: boolean) { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.allowSleep = v; + } + + set mass (value: number) { + let body = this._sharedBody.body; + body.mass = value; + if (body.mass == 0) { + body.type = CANNON.Body.STATIC; + } else { + body.type = this._rigidBody.isKinematic ? CANNON.Body.KINEMATIC : CANNON.Body.DYNAMIC; + } + + body.updateMassProperties(); + if (body.isSleeping()) { + body.wakeUp(); + } + } + + set isKinematic (value: boolean) { + let body = this._sharedBody.body; + if (body.mass == 0) { + body.type = CANNON.Body.STATIC; + } else { + if (value) { + body.type = CANNON.Body.KINEMATIC; + } else { + body.type = CANNON.Body.DYNAMIC; + } + } + } + + set fixedRotation (value: boolean) { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.fixedRotation = value; + body.updateMassProperties(); + } + + set linearDamping (value: number) { + this._sharedBody.body.linearDamping = value; + } + + set angularDamping (value: number) { + this._sharedBody.body.angularDamping = value; + } + + set useGravity (value: boolean) { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.useGravity = value; + } + + set linearFactor (value: cc.Vec3) { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + Vec3.copy(body.linearFactor, value); + } + + set angularFactor (value: cc.Vec3) { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + Vec3.copy(body.angularFactor, value); + } + + get rigidBody () { + return this._rigidBody; + } + + get sharedBody () { + return this._sharedBody; + } + + get isEnabled () { + return this._isEnabled; + } + + private _rigidBody!: RigidBody3D; + private _sharedBody!: CannonSharedBody; + private _isEnabled = false; + + /** LIFECYCLE */ + + __preload (com: RigidBody3D) { + this._rigidBody = com; + this._sharedBody = (cc.director.getPhysics3DManager().physicsWorld as CannonWorld).getSharedBody(this._rigidBody.node); + this._sharedBody.reference = true; + this._sharedBody.wrappedBody = this; + } + + onLoad () { + } + + onEnable () { + this._isEnabled = true; + this.mass = this._rigidBody.mass; + this.allowSleep = this._rigidBody.allowSleep; + this.linearDamping = this._rigidBody.linearDamping; + this.angularDamping = this._rigidBody.angularDamping; + this.useGravity = this._rigidBody.useGravity; + this.isKinematic = this._rigidBody.isKinematic; + this.fixedRotation = this._rigidBody.fixedRotation; + this.linearFactor = this._rigidBody.linearFactor; + this.angularFactor = this._rigidBody.angularFactor; + this._sharedBody.enabled = true; + } + + onDisable () { + this._isEnabled = false; + this._sharedBody.enabled = false; + } + + onDestroy () { + this._sharedBody.reference = false; + (this._rigidBody as any) = null; + (this._sharedBody as any) = null; + } + + /** INTERFACE */ + + wakeUp (): void { + return this._sharedBody.body.wakeUp(); + } + + sleep (): void { + return this._sharedBody.body.sleep(); + } + + getLinearVelocity (out: cc.Vec3): cc.Vec3 { + Vec3.copy(out, this._sharedBody.body.velocity); + return out; + } + + setLinearVelocity (value: cc.Vec3): void { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + + Vec3.copy(body.velocity, value); + } + + getAngularVelocity (out: cc.Vec3): cc.Vec3 { + Vec3.copy(out, this._sharedBody.body.angularVelocity); + return out; + } + + setAngularVelocity (value: cc.Vec3): void { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + Vec3.copy(body.angularVelocity, value); + } + + applyForce (force: cc.Vec3, worldPoint?: cc.Vec3) { + if (worldPoint == null) { + worldPoint = Vec3.ZERO; + } + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.applyForce(Vec3.copy(v3_cannon0, force), Vec3.copy(v3_cannon1, worldPoint)); + } + + applyImpulse (impulse: cc.Vec3, worldPoint?: cc.Vec3) { + if (worldPoint == null) { + worldPoint = Vec3.ZERO; + } + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.applyImpulse(Vec3.copy(v3_cannon0, impulse), Vec3.copy(v3_cannon1, worldPoint)); + } + + applyLocalForce (force: cc.Vec3, localPoint?: cc.Vec3): void { + if (localPoint == null) { + localPoint = Vec3.ZERO; + } + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.applyLocalForce(Vec3.copy(v3_cannon0, force), Vec3.copy(v3_cannon1, localPoint)); + } + + applyLocalImpulse (impulse: cc.Vec3, localPoint?: cc.Vec3): void { + if (localPoint == null) { + localPoint = Vec3.ZERO; + } + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.applyLocalImpulse(Vec3.copy(v3_cannon0, impulse), Vec3.copy(v3_cannon1, localPoint)); + } + + applyTorque (torque: cc.Vec3): void { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + body.torque.x += torque.x; + body.torque.y += torque.y; + body.torque.z += torque.z; + } + + applyLocalTorque (torque: cc.Vec3): void { + let body = this._sharedBody.body; + if (body.isSleeping()) { + body.wakeUp(); + } + Vec3.copy(v3_cannon0, torque); + body.vectorToWorldFrame(v3_cannon0, v3_cannon0); + body.torque.x += v3_cannon0.x; + body.torque.y += v3_cannon0.y; + body.torque.z += v3_cannon0.z; + } +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/cannon/cannon-shared-body.ts b/cocos2d/core/3d/physics/cannon/cannon-shared-body.ts new file mode 100644 index 00000000000..3b825115a2b --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/cannon-shared-body.ts @@ -0,0 +1,261 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../external/cannon/cannon'; +import { ERigidBodyType } from '../framework/physics-enum'; +import { getWrap } from '../framework/util'; +import { CannonWorld } from './cannon-world'; +import { CannonShape } from './shapes/cannon-shape'; +import { Collider3D } from '../exports/physics-framework'; +import { CollisionEventType } from '../framework/physics-interface'; +import { CannonRigidBody } from './cannon-rigid-body'; +import { commitShapeUpdates, groupIndexToBitMask, deprecatedEventMap } from './cannon-util' +import { updateWorldTransform, updateWorldRT } from "../framework/util" + +const LocalDirtyFlag = cc.Node._LocalDirtyFlag; +const PHYSICS_SCALE = LocalDirtyFlag.PHYSICS_SCALE; +const Quat = cc.Quat; +const Vec3 = cc.Vec3; +const fastRemoveAt = cc.js.array.fastRemoveAt; +const v3_0 = new Vec3(); +const quat_0 = new Quat(); +const contactsPool = [] as any; +const CollisionEventObject = { + type: 'collision-enter' as CollisionEventType, + selfCollider: null as Collider3D | null, + otherCollider: null as Collider3D | null, + contacts: [] as any, +}; + +/** + * sharedbody, node : sharedbody = 1 : 1 + * static + */ +export class CannonSharedBody { + + private static readonly sharedBodiesMap = new Map(); + + static getSharedBody (node: cc.Node, wrappedWorld: CannonWorld) { + const key = node._id; + if (CannonSharedBody.sharedBodiesMap.has(key)) { + return CannonSharedBody.sharedBodiesMap.get(key)!; + } else { + const newSB = new CannonSharedBody(node, wrappedWorld); + CannonSharedBody.sharedBodiesMap.set(node._id, newSB); + return newSB; + } + } + + readonly node: cc.Node; + readonly wrappedWorld: CannonWorld; + readonly body: CANNON.Body = new CANNON.Body(); + readonly shapes: CannonShape[] = []; + wrappedBody: CannonRigidBody | null = null; + + private index: number = -1; + private ref: number = 0; + private onCollidedListener = this.onCollided.bind(this); + + /** + * add or remove from world \ + * add, if enable \ + * remove, if disable & shapes.length == 0 & wrappedBody disable + */ + set enabled (v: boolean) { + if (v) { + if (this.index < 0) { + this.index = this.wrappedWorld.bodies.length; + this.wrappedWorld.addSharedBody(this); + + var node = this.node; + // body world aabb need to be recalculated + this.body.aabbNeedsUpdate = true; + node.getWorldPosition(v3_0); + node.getWorldRotation(quat_0); + var pos = this.body.position; + pos.x = parseFloat(v3_0.x.toFixed(3)); + pos.y = parseFloat(v3_0.y.toFixed(3)); + pos.z = parseFloat(v3_0.z.toFixed(3)); + var rot = this.body.quaternion; + rot.x = parseFloat(quat_0.x.toFixed(12)); + rot.y = parseFloat(quat_0.y.toFixed(12)); + rot.z = parseFloat(quat_0.z.toFixed(12)); + rot.w = parseFloat(quat_0.w.toFixed(12)); + + if (node._localMatDirty & PHYSICS_SCALE) { + var wscale = node.__wscale; + for (var i = 0; i < this.shapes.length; i++) { + this.shapes[i].setScale(wscale); + } + commitShapeUpdates(this.body); + } + + if (this.body.isSleeping()) { + this.body.wakeUp(); + } + } + } else { + if (this.index >= 0) { + const isRemove = (this.shapes.length == 0 && this.wrappedBody == null) || + (this.shapes.length == 0 && this.wrappedBody != null && !this.wrappedBody.rigidBody.enabledInHierarchy) || + (this.shapes.length == 0 && this.wrappedBody != null && !this.wrappedBody.isEnabled) + + if (isRemove) { + this.body.sleep(); // clear velocity etc. + this.index = -1; + this.wrappedWorld.removeSharedBody(this); + } + } + } + } + + set reference (v: boolean) { + v ? this.ref++ : this.ref--; + if (this.ref == 0) { this.destroy(); } + } + + private constructor (node: cc.Node, wrappedWorld: CannonWorld) { + this.wrappedWorld = wrappedWorld; + this.node = node; + this.body.material = this.wrappedWorld.world.defaultMaterial; + this.body.addEventListener('cc-collide', this.onCollidedListener); + this._updateGroup(); + this.node.on(cc.Node.EventType.GROUP_CHANGED, this._updateGroup, this); + } + + _updateGroup () { + groupIndexToBitMask(this.node.groupIndex, this.body); + } + + addShape (v: CannonShape) { + const index = this.shapes.indexOf(v); + if (index < 0) { + const index = this.body.shapes.length; + this.body.addShape(v.shape); + this.shapes.push(v); + + v.setIndex(index); + const offset = this.body.shapeOffsets[index]; + const orient = this.body.shapeOrientations[index]; + v.setOffsetAndOrient(offset, orient); + } + } + + removeShape (v: CannonShape) { + const index = this.shapes.indexOf(v); + if (index >= 0) { + fastRemoveAt(this.shapes, index); + this.body.removeShape(v.shape); + v.setIndex(-1); + } + } + + syncSceneToPhysics (force = false) { + let node = this.node; + let needUpdateTransform = updateWorldTransform(node, force); + if (!force && !needUpdateTransform) { + return; + } + // body world aabb need to be recalculated + this.body.aabbNeedsUpdate = true; + + Vec3.copy(this.body.position, node.__wpos); + Quat.copy(this.body.quaternion, node.__wrot); + + if (node._localMatDirty & PHYSICS_SCALE) { + let wscale = node.__wscale; + for (let i = 0; i < this.shapes.length; i++) { + this.shapes[i].setScale(wscale); + } + commitShapeUpdates(this.body); + } + + if (this.body.isSleeping()) { + this.body.wakeUp(); + } + } + + syncPhysicsToScene () { + if (this.body.type != ERigidBodyType.STATIC && !this.body.isSleeping()) { + Vec3.copy(v3_0, this.body.position); + Quat.copy(quat_0, this.body.quaternion); + updateWorldRT(this.node, v3_0, quat_0); + } + } + + private destroy () { + this.body.removeEventListener('cc-collide', this.onCollidedListener); + this.node.off(cc.Node.EventType.GROUP_CHANGED, this._updateGroup, this); + CannonSharedBody.sharedBodiesMap.delete(this.node._id); + delete CANNON.World['idToBodyMap'][this.body.id]; + (this.node as any) = null; + (this.wrappedWorld as any) = null; + (this.body as any) = null; + (this.shapes as any) = null; + (this.onCollidedListener as any) = null; + } + + private onCollided (event: CANNON.ICollisionEvent) { + CollisionEventObject.type = event.event; + const self = getWrap(event.selfShape); + const other = getWrap(event.otherShape); + + if (self) { + CollisionEventObject.selfCollider = self.collider; + CollisionEventObject.otherCollider = other ? other.collider : null; + let i = 0; + for (i = CollisionEventObject.contacts.length; i--;) { + contactsPool.push(CollisionEventObject.contacts.pop()); + } + + for (i = 0; i < event.contacts.length; i++) { + const cq = event.contacts[i]; + if (contactsPool.length > 0) { + const c = contactsPool.pop(); + Vec3.copy(c.contactA, cq.ri); + Vec3.copy(c.contactB, cq.rj); + Vec3.copy(c.normal, cq.ni); + CollisionEventObject.contacts.push(c); + } else { + const c = { + contactA: Vec3.copy(new Vec3(), cq.ri), + contactB: Vec3.copy(new Vec3(), cq.rj), + normal: Vec3.copy(new Vec3(), cq.ni), + }; + CollisionEventObject.contacts.push(c); + } + } + + for (i = 0; i < this.shapes.length; i++) { + const shape = this.shapes[i]; + CollisionEventObject.type = deprecatedEventMap[CollisionEventObject.type]; + shape.collider.emit(CollisionEventObject.type, CollisionEventObject); + // adapt + CollisionEventObject.type = event.event; + shape.collider.emit(CollisionEventObject.type, CollisionEventObject); + } + } + } +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/cannon/cannon-util.ts b/cocos2d/core/3d/physics/cannon/cannon-util.ts new file mode 100644 index 00000000000..02ed4761625 --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/cannon-util.ts @@ -0,0 +1,77 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../external/cannon/cannon'; +import { getWrap } from '../framework/util'; +import { IBaseShape } from '../spec/i-physics-shape'; +import { PhysicsRayResult } from '../framework'; +import { IRaycastOptions } from '../spec/i-physics-world'; + +const Vec3 = cc.Vec3; + +export function groupIndexToBitMask (groupIndex: number, out: { collisionFilterGroup: number; collisionFilterMask: number; }) { + let categoryBits = 1 << groupIndex; + let maskBits = 0; + let bits = cc.game.collisionMatrix[groupIndex]; + if (!bits) { + cc.error("cannon-utils: group is not exist", groupIndex); + return; + } + for (let i = 0; i < bits.length; i++) { + if (!bits[i]) continue; + maskBits |= 1 << i; + } + out.collisionFilterGroup = categoryBits; + out.collisionFilterMask = maskBits; +} + +export function toCannonRaycastOptions (out: CANNON.IRaycastOptions, options: IRaycastOptions) { + out.checkCollisionResponse = !options.queryTrigger; + groupIndexToBitMask(options.groupIndex, out); + out.skipBackFaces = false; +} + +export function fillRaycastResult (result: PhysicsRayResult, cannonResult: CANNON.RaycastResult) { + result._assign( + Vec3.copy(new Vec3(), cannonResult.hitPointWorld), + cannonResult.distance, + getWrap(cannonResult.shape).collider + ); +} + +export function commitShapeUpdates (body: CANNON.Body) { + body.aabbNeedsUpdate = true; + body.updateMassProperties(); + body.updateBoundingRadius(); +} + +export const deprecatedEventMap = { + 'onCollisionEnter': 'collision-enter', + 'onCollisionStay': 'collision-stay', + 'onCollisionExit': 'collision-exit', + 'onTriggerEnter': 'trigger-enter', + 'onTriggerStay': 'trigger-stay', + 'onTriggerExit': 'trigger-exit', +}; diff --git a/cocos2d/core/3d/physics/cannon/cannon-world.ts b/cocos2d/core/3d/physics/cannon/cannon-world.ts new file mode 100644 index 00000000000..736471fc8fb --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/cannon-world.ts @@ -0,0 +1,183 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../external/cannon/cannon'; +import { fillRaycastResult, toCannonRaycastOptions } from './cannon-util'; +import { CannonShape } from './shapes/cannon-shape'; +import { CannonSharedBody } from './cannon-shared-body'; +import { IPhysicsWorld, IRaycastOptions } from '../spec/i-physics-world'; +import { PhysicsMaterial, PhysicsRayResult } from '../framework'; +import { clearNodeTransformRecord, clearNodeTransformDirtyFlag } from '../framework/util' + +const Vec3 = cc.Vec3; +const fastRemoveAt = cc.js.array.fastRemoveAt; + +export class CannonWorld implements IPhysicsWorld { + + get world () { + return this._world; + } + + set defaultMaterial (mat: PhysicsMaterial) { + this._world.defaultMaterial.friction = mat.friction; + this._world.defaultMaterial.restitution = mat.restitution; + if (CannonShape.idToMaterial[mat._uuid] != null) { + CannonShape.idToMaterial[mat._uuid] = this._world.defaultMaterial; + } + } + + set allowSleep (v: boolean) { + this._world.allowSleep = v; + } + + set gravity (gravity: cc.Vec3) { + Vec3.copy(this._world.gravity, gravity); + } + + readonly bodies: CannonSharedBody[] = []; + + private _world: CANNON.World; + private _raycastResult = new CANNON.RaycastResult(); + + constructor () { + this._world = new CANNON.World(); + this._world.broadphase = new CANNON.NaiveBroadphase(); + this._world.addEventListener("postStep", this.onPostStep.bind(this)); + } + + onPostStep () { + const p3dm = cc.director.getPhysics3DManager(); + if (p3dm.useFixedDigit) { + const pd = p3dm.fixDigits.position; + const rd = p3dm.fixDigits.rotation; + const bodies = this._world.bodies; + for (let i = 0; i < bodies.length; i++) { + const bi = bodies[i]; + if(bi.type != CANNON.Body.STATIC && !bi.isSleeping()){ + const pos = bi.position; + pos.x = parseFloat(pos.x.toFixed(pd)); + pos.y = parseFloat(pos.y.toFixed(pd)); + pos.z = parseFloat(pos.z.toFixed(pd)); + const rot = bi.quaternion; + rot.x = parseFloat(rot.x.toFixed(rd)); + rot.y = parseFloat(rot.y.toFixed(rd)); + rot.z = parseFloat(rot.z.toFixed(rd)); + rot.w = parseFloat(rot.w.toFixed(rd)); + const vel = bi.velocity; + vel.x = parseFloat(vel.x.toFixed(pd)); + vel.y = parseFloat(vel.y.toFixed(pd)); + vel.z = parseFloat(vel.z.toFixed(pd)); + const avel = bi.angularVelocity; + avel.x = parseFloat(avel.x.toFixed(pd)); + avel.y = parseFloat(avel.y.toFixed(pd)); + avel.z = parseFloat(avel.z.toFixed(pd)); + } + } + } + } + + step (deltaTime: number, timeSinceLastCalled?: number, maxSubStep?: number) { + this.syncSceneToPhysics(); + this._world.step(deltaTime, timeSinceLastCalled, maxSubStep); + this.syncPhysicsToScene(); + this.emitEvents(); + } + + syncSceneToPhysics () { + clearNodeTransformRecord(); + // sync scene to physics + for (let i = 0; i < this.bodies.length; i++) { + this.bodies[i].syncSceneToPhysics(); + } + clearNodeTransformDirtyFlag(); + } + + syncPhysicsToScene () { + // sync physics to scene + for (let i = 0; i < this.bodies.length; i++) { + this.bodies[i].syncPhysicsToScene(); + } + } + + emitEvents () { + this._world.emitTriggeredEvents(); + this._world.emitCollisionEvents(); + } + + raycastClosest (worldRay: cc.geomUtils.Ray, options: IRaycastOptions, result: PhysicsRayResult): boolean { + setupFromAndTo(worldRay, options.maxDistance); + toCannonRaycastOptions(raycastOpt, options); + const hit = this._world.raycastClosest(from, to, raycastOpt, this._raycastResult); + if (hit) { + fillRaycastResult(result, this._raycastResult); + } + return hit; + } + + raycast (worldRay: cc.geomUtils.Ray, options: IRaycastOptions, pool: cc.RecyclePool, results: PhysicsRayResult[]): boolean { + setupFromAndTo(worldRay, options.maxDistance); + toCannonRaycastOptions(raycastOpt, options); + const hit = this._world.raycastAll(from, to, raycastOpt, (result: CANNON.RaycastResult): any => { + const r = pool.add(); + fillRaycastResult(r, result); + results.push(r); + }); + return hit + } + + getSharedBody (node: Node): CannonSharedBody { + return CannonSharedBody.getSharedBody(node, this); + } + + addSharedBody (sharedBody: CannonSharedBody) { + const i = this.bodies.indexOf(sharedBody); + if (i < 0) { + this.bodies.push(sharedBody); + this._world.addBody(sharedBody.body); + } + } + + removeSharedBody (sharedBody: CannonSharedBody) { + const i = this.bodies.indexOf(sharedBody); + if (i >= 0) { + fastRemoveAt(this.bodies, i); + this._world.remove(sharedBody.body); + } + } +} + +const from = new CANNON.Vec3(); +const to = new CANNON.Vec3(); +function setupFromAndTo (worldRay: cc.geomUtils.Ray, distance: number) { + Vec3.copy(from, worldRay.o); + worldRay.computeHit(to, distance); +} + +const raycastOpt: CANNON.IRaycastOptions = { + 'checkCollisionResponse': false, + 'collisionFilterGroup': -1, + 'collisionFilterMask': -1, + 'skipBackFaces': false +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/cannon/instantiate.ts b/cocos2d/core/3d/physics/cannon/instantiate.ts new file mode 100644 index 00000000000..d82ae3de43d --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/instantiate.ts @@ -0,0 +1,39 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { instantiate } from '../framework/physics-selector'; +import { CannonRigidBody } from './cannon-rigid-body'; +import { CannonWorld } from './cannon-world'; +import { CannonBoxShape } from './shapes/cannon-box-shape'; +import { CannonSphereShape } from './shapes/cannon-sphere-shape'; + +if (CC_PHYSICS_CANNON) { + instantiate( + CannonBoxShape, + CannonSphereShape, + CannonRigidBody, + CannonWorld, + ); +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/cannon/shapes/cannon-box-shape.ts b/cocos2d/core/3d/physics/cannon/shapes/cannon-box-shape.ts new file mode 100644 index 00000000000..23ccb1a5ece --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/shapes/cannon-box-shape.ts @@ -0,0 +1,72 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../../external/cannon/cannon'; +import { commitShapeUpdates } from '../cannon-util'; +import { CannonShape } from './cannon-shape'; +import { IBoxShape } from '../../spec/i-physics-shape'; +import { IVec3Like } from '../../spec/i-common'; +import { BoxCollider3D } from '../../exports/physics-framework'; + +const Vec3 = cc.Vec3; +const v3_0 = new Vec3(); + +export class CannonBoxShape extends CannonShape implements IBoxShape { + + public get boxCollider () { + return this.collider as BoxCollider3D; + } + + public get box () { + return this._shape as CANNON.Box; + } + + readonly halfExtent: CANNON.Vec3 = new CANNON.Vec3(); + constructor (size: cc.Vec3) { + super(); + Vec3.multiplyScalar(this.halfExtent, size, 0.5); + this._shape = new CANNON.Box(this.halfExtent.clone()); + } + + set size (v: IVec3Like) { + this.collider.node.getWorldScale(v3_0); + Vec3.multiplyScalar(this.halfExtent, v, 0.5); + Vec3.multiply(this.box.halfExtents, this.halfExtent, v3_0); + this.box.updateConvexPolyhedronRepresentation(); + if (this._index != -1) { + commitShapeUpdates(this._body); + } + } + + onLoad () { + super.onLoad(); + this.size = this.boxCollider.size; + } + + setScale (scale: cc.Vec3): void { + super.setScale(scale); + this.size = this.boxCollider.size; + } +} diff --git a/cocos2d/core/3d/physics/cannon/shapes/cannon-shape.ts b/cocos2d/core/3d/physics/cannon/shapes/cannon-shape.ts new file mode 100644 index 00000000000..665aab96649 --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/shapes/cannon-shape.ts @@ -0,0 +1,179 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../../external/cannon/cannon'; +import { getWrap, setWrap } from '../../framework/util'; +import { commitShapeUpdates, deprecatedEventMap } from '../cannon-util'; +import { PhysicsMaterial } from '../../framework/assets/physics-material'; +import { IBaseShape } from '../../spec/i-physics-shape'; +import { IVec3Like } from '../../spec/i-common'; +import { CannonSharedBody } from '../cannon-shared-body'; +import { CannonWorld } from '../cannon-world'; +import { TriggerEventType } from '../../framework/physics-interface'; +import { Collider3D } from '../../framework'; + +const TriggerEventObject = { + type: 'trigger-enter' as TriggerEventType, + selfCollider: null as Collider3D | null, + otherCollider: null as Collider3D | null, +}; + +const Vec3 = cc.Vec3; +const v3_0 = new Vec3(); + +export class CannonShape implements IBaseShape { + + static readonly idToMaterial = {}; + + get shape () { return this._shape!; } + + get collider () { return this._collider; } + + get attachedRigidBody () { + if (this._sharedBody.wrappedBody) { return this._sharedBody.wrappedBody.rigidBody; } + return null; + } + + get sharedBody (): CannonSharedBody { return this._sharedBody; } + + set material (mat: PhysicsMaterial) { + if (mat == null) { + (this._shape!.material as unknown) = null; + } else { + if (CannonShape.idToMaterial[mat._uuid] == null) { + CannonShape.idToMaterial[mat._uuid] = new CANNON.Material(mat._uuid); + } + + this._shape!.material = CannonShape.idToMaterial[mat._uuid]; + this._shape!.material.friction = mat.friction; + this._shape!.material.restitution = mat.restitution; + } + } + + set isTrigger (v: boolean) { + this._shape.collisionResponse = !v; + if (this._index >= 0) { + this._body.updateHasTrigger(); + } + } + + set center (v: IVec3Like) { + this._setCenter(v); + if (this._index >= 0) { + commitShapeUpdates(this._body); + } + } + + _collider!: Collider3D; + + protected _shape!: CANNON.Shape; + protected _offset = new CANNON.Vec3(); + protected _orient = new CANNON.Quaternion(); + protected _index: number = -1; + protected _sharedBody!: CannonSharedBody; + protected get _body (): CANNON.Body { return this._sharedBody.body; } + protected onTriggerListener = this.onTrigger.bind(this); + + /** LIFECYCLE */ + + __preload (comp: Collider3D) { + this._collider = comp; + setWrap(this._shape, this); + this._shape.addEventListener('cc-trigger', this.onTriggerListener); + this._sharedBody = (cc.director.getPhysics3DManager().physicsWorld as CannonWorld).getSharedBody(this._collider.node); + this._sharedBody.reference = true; + } + + onLoad () { + this.center = this._collider.center; + this.isTrigger = this._collider.isTrigger; + } + + onEnable () { + this._sharedBody.addShape(this); + this._sharedBody.enabled = true; + } + + onDisable () { + this._sharedBody.removeShape(this); + this._sharedBody.enabled = false; + } + + onDestroy () { + this._sharedBody.reference = false; + this._shape.removeEventListener('cc-trigger', this.onTriggerListener); + delete CANNON.World['idToShapeMap'][this._shape.id]; + (this._sharedBody as any) = null; + setWrap(this._shape, null); + (this._offset as any) = null; + (this._orient as any) = null; + (this._shape as any) = null; + (this._collider as any) = null; + (this.onTriggerListener as any) = null; + } + + /** + * change scale will recalculate center & size \ + * size handle by child class + * @param scale + */ + setScale (scale: IVec3Like) { + this._setCenter(this._collider.center); + } + + setIndex (index: number) { + this._index = index; + } + + setOffsetAndOrient (offset: CANNON.Vec3, orient: CANNON.Quaternion) { + cc.Vec3.copy(offset, this._offset); + cc.Vec3.copy(orient, this._orient); + this._offset = offset; + this._orient = orient; + } + + protected _setCenter (v: IVec3Like) { + const lpos = this._offset as IVec3Like; + Vec3.copy(lpos, v); + this._collider.node.getWorldScale(v3_0); + Vec3.multiply(lpos, lpos, v3_0); + } + + private onTrigger (event: CANNON.ITriggeredEvent) { + TriggerEventObject.type = event.event; + const self = getWrap(event.selfShape); + const other = getWrap(event.otherShape); + + if (self) { + TriggerEventObject.selfCollider = self.collider; + TriggerEventObject.otherCollider = other ? other.collider : null; + TriggerEventObject.type = deprecatedEventMap[TriggerEventObject.type]; + this._collider.emit(TriggerEventObject.type, TriggerEventObject); + // adapt + TriggerEventObject.type = event.event; + this._collider.emit(TriggerEventObject.type, TriggerEventObject); + } + } +} diff --git a/cocos2d/core/3d/physics/cannon/shapes/cannon-sphere-shape.ts b/cocos2d/core/3d/physics/cannon/shapes/cannon-sphere-shape.ts new file mode 100644 index 00000000000..1a83e4f8a87 --- /dev/null +++ b/cocos2d/core/3d/physics/cannon/shapes/cannon-sphere-shape.ts @@ -0,0 +1,75 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import CANNON from '../../../../../../external/cannon/cannon'; +import { commitShapeUpdates } from '../cannon-util'; +import { CannonShape } from './cannon-shape'; +import { ISphereShape } from '../../spec/i-physics-shape'; +import { SphereCollider3D } from '../../exports/physics-framework'; + +const v3_0 = new cc.Vec3(); +export class CannonSphereShape extends CannonShape implements ISphereShape { + + get sphereCollider () { + return this.collider as SphereCollider3D; + } + + get sphere () { + return this._shape as CANNON.Sphere; + } + + get radius () { + return this._radius; + } + + set radius (v: number) { + this.collider.node.getWorldScale(v3_0); + const max = v3_0.maxAxis(); + this.sphere.radius = v * Math.abs(max); + this.sphere.updateBoundingSphereRadius(); + if (this._index != -1) { + commitShapeUpdates(this._body); + } + } + + private _radius: number; + + constructor (radius: number) { + super(); + this._radius = radius; + this._shape = new CANNON.Sphere(this._radius); + } + + onLoad () { + super.onLoad(); + this.radius = this.sphereCollider.radius; + } + + setScale (scale: cc.Vec3): void { + super.setScale(scale); + this.radius = this.sphereCollider.radius; + } + +} diff --git a/cocos2d/core/3d/physics/cocos/builtin-interface.ts b/cocos2d/core/3d/physics/cocos/builtin-interface.ts new file mode 100644 index 00000000000..39370a4966f --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/builtin-interface.ts @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import {IVec3Like, IQuatLike} from "../spec/i-common" + +/** + * declare interface + */ +export interface IBuiltinShape { + center: cc.Vec3; + transform (m: cc.Mat4, pos: IVec3Like, rot: IQuatLike, scale: IVec3Like, out: IBuiltinShape): any; +} diff --git a/cocos2d/core/3d/physics/cocos/builtin-shared-body.ts b/cocos2d/core/3d/physics/cocos/builtin-shared-body.ts new file mode 100644 index 00000000000..3b60765a851 --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/builtin-shared-body.ts @@ -0,0 +1,140 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +import { BuiltInWorld } from './builtin-world'; +import { BuiltinShape } from './shapes/builtin-shape'; +import { updateWorldTransform } from "../framework/util" + +const intersect = cc.geomUtils.intersect; +const fastRemove = cc.js.array.fastRemove; + +/** + * Built-in static collider, no physical forces involved + */ +export class BuiltinSharedBody { + + private static readonly sharedBodiesMap = new Map(); + + static getSharedBody (node: cc.Node, wrappedWorld: BuiltInWorld) { + const key = node._id; + if (BuiltinSharedBody.sharedBodiesMap.has(key)) { + return BuiltinSharedBody.sharedBodiesMap.get(key)!; + } else { + const newSB = new BuiltinSharedBody(node, wrappedWorld); + BuiltinSharedBody.sharedBodiesMap.set(node._id, newSB); + return newSB; + } + } + + get id () { + return this._id; + } + + /** + * add or remove from world \ + * add, if enable \ + * remove, if disable & shapes.length == 0 & wrappedBody disable + */ + set enabled (v: boolean) { + if (v) { + if (this.index < 0) { + this.index = this.world.bodies.length; + this.world.addSharedBody(this); + this.syncSceneToPhysics(true); + } + } else { + if (this.index >= 0) { + const isRemove = (this.shapes.length == 0); + if (isRemove) { + this.index = -1; + this.world.removeSharedBody(this); + } + } + } + } + + set reference (v: boolean) { + v ? this.ref++ : this.ref--; + if (this.ref == 0) { this.destory(); } + } + + /** id generator */ + private static idCounter: number = 0; + private readonly _id: number; + private index: number = -1; + private ref: number = 0; + + readonly node: cc.Node; + readonly world: BuiltInWorld; + readonly shapes: BuiltinShape[] = []; + + private constructor (node: cc.Node, world: BuiltInWorld) { + this._id = BuiltinSharedBody.idCounter++; + this.node = node; + this.world = world; + } + + intersects (body: BuiltinSharedBody) { + for (let i = 0; i < this.shapes.length; i++) { + const shapeA = this.shapes[i]; + + for (let j = 0; j < body.shapes.length; j++) { + const shapeB = body.shapes[j]; + + if (intersect.resolve(shapeA.worldShape, shapeB.worldShape)) { + this.world.shapeArr.push(shapeA); + this.world.shapeArr.push(shapeB); + } + } + } + } + + addShape (shape: BuiltinShape): void { + const i = this.shapes.indexOf(shape); + if (i < 0) { + this.shapes.push(shape); + } + } + + removeShape (shape: BuiltinShape): void { + fastRemove(this.shapes, shape); + } + + syncSceneToPhysics (force: boolean = false) { + let node = this.node; + let needUpdateTransform = updateWorldTransform(node, force); + if (!force && !needUpdateTransform) return; + + for (let i = 0; i < this.shapes.length; i++) { + this.shapes[i].transform(node._worldMatrix, node.__wpos, node.__wrot, node.__wscale); + } + } + + private destory () { + BuiltinSharedBody.sharedBodiesMap.delete(this.node._id); + (this.node as any) = null; + (this.world as any) = null; + (this.shapes as any) = null; + } +} diff --git a/cocos2d/core/3d/physics/cocos/builtin-world.ts b/cocos2d/core/3d/physics/cocos/builtin-world.ts new file mode 100644 index 00000000000..287e8574df7 --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/builtin-world.ts @@ -0,0 +1,237 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { PhysicsRayResult } from '../framework/physics-ray-result'; +import { BuiltinSharedBody } from './builtin-shared-body'; +import { BuiltinShape } from './shapes/builtin-shape'; +import { ArrayCollisionMatrix } from './utils/array-collision-matrix'; +import { IPhysicsWorld, IRaycastOptions } from '../spec/i-physics-world'; +import { IVec3Like } from '../spec/i-common'; +import { PhysicsMaterial } from './../framework/assets/physics-material'; +import { TriggerEventType } from '../framework/physics-interface'; +import { Collider3D } from '../exports/physics-framework'; +import { clearNodeTransformRecord, clearNodeTransformDirtyFlag } from '../framework/util'; + +const fastRemove = cc.js.array.fastRemove; +const intersect = cc.geomUtils.intersect; +const Vec3 = cc.Vec3; +const hitPoint = new Vec3(); +const TriggerEventObject = { + type: 'collision-enter' as unknown as TriggerEventType, + selfCollider: null as unknown as Collider3D, + otherCollider: null as unknown as Collider3D, +}; + +/** + * Built-in collision system, intended for use as a + * efficient discrete collision detector, + * not a full physical simulator + */ +export class BuiltInWorld implements IPhysicsWorld { + set gravity (v: IVec3Like) { } + set allowSleep (v: boolean) { } + set defaultMaterial (v: PhysicsMaterial) { } + + readonly shapeArr: BuiltinShape[] = []; + readonly bodies: BuiltinSharedBody[] = []; + + private _shapeArrOld: BuiltinShape[] = []; + private _collisionMatrix: ArrayCollisionMatrix = new ArrayCollisionMatrix(); + private _collisionMatrixPrev: ArrayCollisionMatrix = new ArrayCollisionMatrix(); + + step (): void { + // store and reset collsion array + this._shapeArrOld = this.shapeArr.slice(); + this.shapeArr.length = 0; + + clearNodeTransformRecord(); + + // sync scene to collision + for (let i = 0; i < this.bodies.length; i++) { + this.bodies[i].syncSceneToPhysics(); + } + + clearNodeTransformDirtyFlag(); + + const collisionMatrix = cc.game.collisionMatrix; + + // collision detection + for (let i = 0; i < this.bodies.length; i++) { + const bodyA = this.bodies[i]; + const nodeA = bodyA.node; + const nodeACollisionMatrix = collisionMatrix[nodeA.groupIndex]; + if (!nodeACollisionMatrix) continue; + for (let j = i + 1; j < this.bodies.length; j++) { + const bodyB = this.bodies[j]; + const nodeB = bodyB.node; + if (nodeA !== nodeB && nodeACollisionMatrix[nodeB.groupIndex]) { + bodyA.intersects(bodyB); + } + } + } + + // emit collider event + this.emitColliderEvent(); + } + + raycastClosest (worldRay: cc.geomUtils.Ray, options: IRaycastOptions, out: PhysicsRayResult): boolean { + let tmp_d = Infinity; + const max_d = options.maxDistance!; + const groupIndex = options.groupIndex!; + const collisionMatrix = cc.game.collisionMatrix; + const rayCollisionMatrix = collisionMatrix[groupIndex]; + if (!rayCollisionMatrix) return false; + + for (let i = 0; i < this.bodies.length; i++) { + const body = this.bodies[i] as BuiltinSharedBody; + const bodyGroupIndex = body.node.groupIndex; + const canCollider = rayCollisionMatrix[bodyGroupIndex]; + if (!canCollider) continue; + for (let i = 0; i < body.shapes.length; i++) { + const shape = body.shapes[i]; + const distance = intersect.resolve(worldRay, shape.worldShape); + if (distance == 0 || distance > max_d) { + continue; + } + if (tmp_d > distance) { + tmp_d = distance; + Vec3.normalize(hitPoint, worldRay.d) + Vec3.scaleAndAdd(hitPoint, worldRay.o, hitPoint, distance); + out._assign(hitPoint, distance, shape.collider); + } + } + } + + return !(tmp_d == Infinity); + } + + raycast (worldRay: cc.geomUtils.Ray, options: IRaycastOptions, pool: cc.RecyclePool, results: PhysicsRayResult[]): boolean { + const max_d = options.maxDistance!; + const groupIndex = options.groupIndex!; + const collisionMatrix = cc.game.collisionMatrix; + const rayCollisionMatrix = collisionMatrix[groupIndex]; + if (!rayCollisionMatrix) return false; + + for (let i = 0; i < this.bodies.length; i++) { + const body = this.bodies[i] as BuiltinSharedBody; + const bodyGroupIndex = body.node.groupIndex; + const canCollider = rayCollisionMatrix[bodyGroupIndex]; + if (!canCollider) continue; + for (let i = 0; i < body.shapes.length; i++) { + const shape = body.shapes[i]; + const distance = intersect.resolve(worldRay, shape.worldShape); + if (distance == 0 || distance > max_d) { + continue; + } else { + const r = pool.add(); + worldRay.computeHit(hitPoint, distance); + r._assign(hitPoint, distance, shape.collider); + results.push(r); + } + } + } + return results.length > 0; + } + + getSharedBody (node: cc.Node): BuiltinSharedBody { + return BuiltinSharedBody.getSharedBody(node, this); + } + + addSharedBody (body: BuiltinSharedBody) { + const index = this.bodies.indexOf(body); + if (index < 0) { + this.bodies.push(body); + } + } + + removeSharedBody (body: BuiltinSharedBody) { + fastRemove(this.bodies, body); + } + + private emitColliderEvent () { + let shapeA: BuiltinShape; + let shapeB: BuiltinShape; + for (let i = 0; i < this.shapeArr.length; i += 2) { + shapeA = this.shapeArr[i]; + shapeB = this.shapeArr[i + 1]; + + TriggerEventObject.selfCollider = shapeA.collider; + TriggerEventObject.otherCollider = shapeB.collider; + + this._collisionMatrix.set(shapeA.id, shapeB.id, true); + + if (this._collisionMatrixPrev.get(shapeA.id, shapeB.id)) { + // emit stay + TriggerEventObject.type = 'trigger-stay'; + } else { + // first collider, emit enter + TriggerEventObject.type = 'trigger-enter'; + } + + if (shapeA.collider) { + shapeA.collider.emit(TriggerEventObject.type, TriggerEventObject); + } + + TriggerEventObject.selfCollider = shapeB.collider; + TriggerEventObject.otherCollider = shapeA.collider; + + if (shapeB.collider) { + shapeB.collider.emit(TriggerEventObject.type, TriggerEventObject); + } + } + + for (let i = 0; i < this._shapeArrOld.length; i += 2) { + shapeA = this._shapeArrOld[i]; + shapeB = this._shapeArrOld[i + 1]; + + if (this._collisionMatrixPrev.get(shapeA.id, shapeB.id)) { + if (!this._collisionMatrix.get(shapeA.id, shapeB.id)) { + // emit exit + TriggerEventObject.type = 'trigger-exit'; + TriggerEventObject.selfCollider = shapeA.collider; + TriggerEventObject.otherCollider = shapeB.collider; + + if (shapeA.collider) { + shapeA.collider.emit(TriggerEventObject.type, TriggerEventObject); + } + + TriggerEventObject.selfCollider = shapeB.collider; + TriggerEventObject.otherCollider = shapeA.collider; + + if (shapeB.collider) { + shapeB.collider.emit(TriggerEventObject.type, TriggerEventObject); + } + + this._collisionMatrix.set(shapeA.id, shapeB.id, false); + } + } + } + + this._collisionMatrixPrev.matrix = this._collisionMatrix.matrix.slice(); + this._collisionMatrix.reset(); + + } + +} diff --git a/cocos2d/core/3d/physics/cocos/instantiate.ts b/cocos2d/core/3d/physics/cocos/instantiate.ts new file mode 100644 index 00000000000..3180325f1cd --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/instantiate.ts @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { instantiate } from '../framework/physics-selector'; +import { BuiltInWorld } from './builtin-world'; +import { BuiltinBoxShape } from './shapes/builtin-box-shape'; +import { BuiltinSphereShape } from './shapes/builtin-sphere-shape'; + +if (CC_PHYSICS_BUILTIN) { + instantiate( + BuiltinBoxShape, + BuiltinSphereShape, + null, + BuiltInWorld, + ); +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/cocos/shapes/builtin-box-shape.ts b/cocos2d/core/3d/physics/cocos/shapes/builtin-box-shape.ts new file mode 100644 index 00000000000..7aee0c64a2b --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/shapes/builtin-box-shape.ts @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { BuiltinShape } from './builtin-shape'; +import { IBoxShape } from '../../spec/i-physics-shape'; +import { BoxCollider3D } from '../../exports/physics-framework'; + +const Obb = cc.geomUtils.Obb; +const Vec3 = cc.Vec3; +let _worldScale = new Vec3(); + +export class BuiltinBoxShape extends BuiltinShape implements IBoxShape { + + get localObb () { + return this._localShape as cc.geomUtils.Obb; + } + + get worldObb () { + return this._worldShape as cc.geomUtils.Obb; + } + + public get boxCollider () { + return this.collider as BoxCollider3D; + } + + constructor (size: cc.Vec3) { + super(); + this._localShape = new Obb(); + this._worldShape = new Obb(); + Vec3.multiplyScalar(this.localObb.halfExtents, size, 0.5); + Vec3.copy(this.worldObb.halfExtents, this.localObb.halfExtents); + } + + set size (size: cc.Vec3) { + Vec3.multiplyScalar(this.localObb.halfExtents, size, 0.5); + this.collider.node.getWorldScale(_worldScale); + Vec3.multiply(this.worldObb.halfExtents, this.localObb.halfExtents, _worldScale); + } + + onLoad () { + super.onLoad(); + this.size = this.boxCollider.size; + } + +} diff --git a/cocos2d/core/3d/physics/cocos/shapes/builtin-shape.ts b/cocos2d/core/3d/physics/cocos/shapes/builtin-shape.ts new file mode 100644 index 00000000000..2d45e69f214 --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/shapes/builtin-shape.ts @@ -0,0 +1,100 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { BuiltinSharedBody } from '../builtin-shared-body'; +import { IBuiltinShape } from '../builtin-interface'; +import { Collider3D, PhysicsMaterial, RigidBody3D } from '../../exports/physics-framework'; +import { IBaseShape } from '../../spec/i-physics-shape'; +import { IVec3Like } from '../../spec/i-common'; +import { BuiltInWorld } from '../builtin-world'; + +const Vec3 = cc.Vec3; + +export class BuiltinShape implements IBaseShape { + set material (v: PhysicsMaterial) { } + set isTrigger (v: boolean) { } + get attachedRigidBody (): RigidBody3D | null { return null; } + + set center (v: IVec3Like) { + Vec3.copy(this._localShape.center, v); + } + + get localShape () { + return this._worldShape; + } + + get worldShape () { + return this._worldShape; + } + + get sharedBody () { + return this._sharedBody; + } + + get collider () { + return this._collider; + } + + /** id generator */ + private static idCounter: number = 0; + readonly id: number = BuiltinShape.idCounter++;; + + protected _sharedBody!: BuiltinSharedBody; + protected _collider!: Collider3D; + protected _localShape!: IBuiltinShape; + protected _worldShape!: IBuiltinShape; + + __preload (comp: Collider3D) { + this._collider = comp; + this._sharedBody = (cc.director.getPhysics3DManager().physicsWorld as BuiltInWorld).getSharedBody(this._collider.node); + this._sharedBody.reference = true; + } + + onLoad () { + this.center = this._collider.center; + } + + onEnable () { + this._sharedBody.addShape(this); + this._sharedBody.enabled = true; + } + + onDisable () { + this._sharedBody.removeShape(this); + this._sharedBody.enabled = false; + } + + onDestroy () { + this._sharedBody.reference = false; + (this._collider as any) = null; + (this._localShape as any) = null; + (this._worldShape as any) = null; + } + + transform (m: cc.Mat4, pos: cc.Vec3, rot: cc.Quat, scale: cc.Vec3) { + this._localShape.transform(m, pos, rot, scale, this._worldShape); + } + +} diff --git a/cocos2d/core/3d/physics/cocos/shapes/builtin-sphere-shape.ts b/cocos2d/core/3d/physics/cocos/shapes/builtin-sphere-shape.ts new file mode 100644 index 00000000000..c91ed8786ac --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/shapes/builtin-sphere-shape.ts @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { BuiltinShape } from './builtin-shape'; +import { ISphereShape } from '../../spec/i-physics-shape'; +import { SphereCollider3D } from '../../exports/physics-framework'; + +const Sphere = cc.geomUtils.Sphere; +let _worldScale = new cc.Vec3(); + +export class BuiltinSphereShape extends BuiltinShape implements ISphereShape { + + set radius (radius: number) { + this.localSphere.radius = radius; + this.collider.node.getWorldScale(_worldScale); + const s = _worldScale.maxAxis(); + this.worldSphere.radius = this.localSphere.radius * s; + } + + get localSphere () { + return this._localShape as cc.geomUtils.Sphere; + } + + get worldSphere () { + return this._worldShape as cc.geomUtils.Sphere; + } + + get sphereCollider () { + return this.collider as SphereCollider3D; + } + + constructor (radius: number) { + super(); + this._localShape = new Sphere(0, 0, 0, radius); + this._worldShape = new Sphere(0, 0, 0, radius); + } + + onLoad () { + super.onLoad(); + this.radius = this.sphereCollider.radius; + } + +} diff --git a/cocos2d/core/3d/physics/cocos/utils/array-collision-matrix.ts b/cocos2d/core/3d/physics/cocos/utils/array-collision-matrix.ts new file mode 100644 index 00000000000..62abe0b2d02 --- /dev/null +++ b/cocos2d/core/3d/physics/cocos/utils/array-collision-matrix.ts @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +/** + * Collision "matrix". It's actually a triangular-shaped array of whether two bodies are touching this step, for reference next step + */ +export class ArrayCollisionMatrix { + + /** + * !#en The matrix storage + * @property matrix + * @type {Array} + */ + public matrix: number[] = []; + + /** + * !#en Get an element + * @method get + * @param {Number} i + * @param {Number} j + * @return {Number} + */ + public get (i: number, j: number): number { + if (j > i) { + const temp = j; + j = i; + i = temp; + } + return this.matrix[(i * (i + 1) >> 1) + j - 1]; + } + + /** + * !#en Set an element + * @method set + * @param {Number} i + * @param {Number} j + * @param {boolean} value + */ + public set (i: number, j: number, value: boolean) { + if (j > i) { + const temp = j; + j = i; + i = temp; + } + this.matrix[(i * (i + 1) >> 1) + j - 1] = value ? 1 : 0; + } + + /** + * !#en Sets all elements to zero + * @method reset + */ + public reset () { + for (let i = 0, l = this.matrix.length; i !== l; i++) { + this.matrix[i] = 0; + } + } + + /** + * !#en Sets the max number of objects + * @param {Number} n + */ + public setNumObjects (n: number) { + this.matrix.length = n * (n - 1) >> 1; + } + +} diff --git a/cocos2d/core/renderer/webgl/assemblers/assembler.js b/cocos2d/core/3d/physics/exports/physics-builtin.ts similarity index 90% rename from cocos2d/core/renderer/webgl/assemblers/assembler.js rename to cocos2d/core/3d/physics/exports/physics-builtin.ts index b9096953f69..759409eff78 100644 --- a/cocos2d/core/renderer/webgl/assemblers/assembler.js +++ b/cocos2d/core/3d/physics/exports/physics-builtin.ts @@ -1,7 +1,7 @@ /**************************************************************************** - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -23,7 +23,4 @@ THE SOFTWARE. ****************************************************************************/ -module.exports = { - useModel: false, - datas: [] -}; \ No newline at end of file +import '../cocos/instantiate'; diff --git a/cocos2d/core/3d/physics/exports/physics-cannon.ts b/cocos2d/core/3d/physics/exports/physics-cannon.ts new file mode 100644 index 00000000000..36ef472c1b6 --- /dev/null +++ b/cocos2d/core/3d/physics/exports/physics-cannon.ts @@ -0,0 +1,29 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import '../cannon/instantiate'; + +import CANNON from '../../../../../external/cannon/cannon'; +if (window) window.CANNON = CANNON; \ No newline at end of file diff --git a/cocos2d/core/3d/physics/exports/physics-framework.ts b/cocos2d/core/3d/physics/exports/physics-framework.ts new file mode 100644 index 00000000000..ad4047c4ec0 --- /dev/null +++ b/cocos2d/core/3d/physics/exports/physics-framework.ts @@ -0,0 +1,26 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +export * from '../framework'; \ No newline at end of file diff --git a/cocos2d/core/3d/physics/framework/assets/physics-material.ts b/cocos2d/core/3d/physics/framework/assets/physics-material.ts new file mode 100644 index 00000000000..34154fd9ba1 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/assets/physics-material.ts @@ -0,0 +1,117 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +const {ccclass, property} = cc._decorator; +const fastRemove = cc.js.array.fastRemove; +const equals = cc.math.equals; + +/** + * !#en + * Physics material. + * !#zh + * 物理材质。 + * @class PhysicsMaterial + * @extends Asset + */ +@ccclass('cc.PhysicsMaterial') +export class PhysicsMaterial extends cc.Asset { + + public static allMaterials: PhysicsMaterial[] = []; + + private static _idCounter: number = 0; + + @property + private _friction = 0.1; + + @property + private _restitution = 0.1; + + /** + * !#en + * Friction for this material. + * !#zh + * 物理材质的摩擦力。 + * @property {number} friction + */ + @property + get friction () { + return this._friction; + } + + set friction (value) { + if (!equals(this._friction, value)) { + this._friction = value; + this.emit('physics_material_update'); + } + } + + /** + * !#en + * Restitution for this material. + * !#zh + * 物理材质的弹力。 + * @property {number} restitution + */ + @property + get restitution () { + return this._restitution; + } + + set restitution (value) { + if (!equals(this._restitution, value)) { + this._restitution = value; + this.emit('physics_material_update'); + } + } + + constructor () { + super(); + cc.EventTarget.call(this); + PhysicsMaterial.allMaterials.push(this); + if (this._uuid == '') { + this._uuid = 'pm_' + PhysicsMaterial._idCounter++; + } + } + + public clone () { + let c = new PhysicsMaterial(); + c._friction = this._friction; + c._restitution = this._restitution; + return c; + } + + public destroy (): boolean { + if (super.destroy()) { + fastRemove(PhysicsMaterial.allMaterials, this); + return true; + } else { + return false; + } + } + +} + +cc.js.mixin(PhysicsMaterial.prototype, cc.EventTarget.prototype); +cc.PhysicsMaterial = PhysicsMaterial; diff --git a/cocos2d/core/3d/physics/framework/components/collider/box-collider-component.ts b/cocos2d/core/3d/physics/framework/components/collider/box-collider-component.ts new file mode 100644 index 00000000000..8b1c7d72831 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/components/collider/box-collider-component.ts @@ -0,0 +1,97 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { createBoxShape } from '../../instance'; +import { Collider3D } from './collider-component'; +import { IBoxShape } from '../../../spec/i-physics-shape'; + +const { + ccclass, + executeInEditMode, + executionOrder, + menu, + property, +} = cc._decorator; + +const Vec3 = cc.Vec3; + +/** + * !#en + * Physics box collider + * !#zh + * 物理盒子碰撞器 + * @class BoxCollider3D + * @extends Collider3D + */ +@ccclass('cc.BoxCollider3D') +@executionOrder(98) +@menu('i18n:MAIN_MENU.component.physics/Collider/Box 3D') +@executeInEditMode +export class BoxCollider3D extends Collider3D { + + /// PUBLIC PROPERTY GETTER\SETTER /// + + /** + * !#en + * Get or set the size of the box, in local space. + * !#zh + * 获取或设置盒的大小。 + * @property {Vec3} size + */ + @property({ + type: cc.Vec3 + }) + public get size () { + return this._size; + } + + public set size (value) { + Vec3.copy(this._size, value); + if (!CC_EDITOR) { + this.boxShape.size = this._size; + } + } + + /** + * @property {IBoxShape} boxShape + * @readonly + */ + public get boxShape (): IBoxShape { + return this._shape as IBoxShape; + } + + /// PRIVATE PROPERTY /// + + @property + private _size: cc.Vec3 = new Vec3(1, 1, 1); + + constructor () { + super(); + if (!CC_EDITOR) { + this._shape = createBoxShape(this._size); + } + } + +} diff --git a/cocos2d/core/3d/physics/framework/components/collider/collider-component.ts b/cocos2d/core/3d/physics/framework/components/collider/collider-component.ts new file mode 100644 index 00000000000..b670dd32141 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/components/collider/collider-component.ts @@ -0,0 +1,310 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { CollisionCallback, CollisionEventType, TriggerCallback, TriggerEventType, ICollisionEvent } from '../../physics-interface'; +import { RigidBody3D } from '../rigid-body-component'; +import { PhysicsMaterial } from '../../assets/physics-material'; +import { IBaseShape } from '../../../spec/i-physics-shape'; + +const {ccclass, property} = cc._decorator; +const Vec3 = cc.Vec3; + +/** + * !#en + * The base class of the collider. + * !#zh + * 碰撞器的基类。 + * @class Collider3D + * @extends Component + * @uses EventTarget + */ +@ccclass('cc.Collider3D') +export class Collider3D extends cc.Component { + + /** + * @property {PhysicsMaterial} sharedMaterial + */ + @property({ + type: PhysicsMaterial, + displayName: 'Material', + displayOrder: -1 + }) + public get sharedMaterial () { + return this._material; + } + + public set sharedMaterial (value) { + this.material = value; + } + + public get material () { + if (!CC_PHYSICS_BUILTIN) { + if (this._isSharedMaterial && this._material != null) { + this._material.off('physics_material_update', this._updateMaterial, this); + this._material = this._material.clone(); + this._material.on('physics_material_update', this._updateMaterial, this); + this._isSharedMaterial = false; + } + } + return this._material; + } + + public set material (value) { + if (CC_EDITOR || CC_PHYSICS_BUILTIN) { + this._material = value; + return; + } + if (value != null && this._material != null) { + if (this._material._uuid != value._uuid) { + this._material.off('physics_material_update', this._updateMaterial, this); + value.on('physics_material_update', this._updateMaterial, this); + this._isSharedMaterial = false; + this._material = value; + } + } else if (value != null && this._material == null) { + value.on('physics_material_update', this._updateMaterial, this); + this._material = value; + } else if (value == null && this._material != null) { + this._material!.off('physics_material_update', this._updateMaterial, this); + this._material = value; + } + this._updateMaterial(); + } + + /** + * !#en + * get or set the collider is trigger, this will be always trigger if using builtin. + * !#zh + * 获取或设置碰撞器是否为触发器。 + * @property {Boolean} isTrigger + */ + @property({ + displayOrder: 0 + }) + public get isTrigger () { + return this._isTrigger; + } + + public set isTrigger (value) { + this._isTrigger = value; + if (!CC_EDITOR) { + this._shape.isTrigger = this._isTrigger; + } + } + + /** + * !#en + * get or set the center of the collider, in local space. + * !#zh + * 获取或设置碰撞器的中心点。 + * @property {Vec3} center + */ + @property({ + type: cc.Vec3, + displayOrder: 1 + }) + public get center () { + return this._center; + } + + public set center (value: cc.Vec3) { + Vec3.copy(this._center, value); + if (!CC_EDITOR) { + this._shape.center = this._center; + } + } + + /** + * !#en + * get the collider attached rigidbody, this may be null. + * !#zh + * 获取碰撞器所绑定的刚体组件,可能为 null。 + * @property {RigidBody3D|null} attachedRigidbody + * @readonly + */ + public get attachedRigidbody (): RigidBody3D | null { + return this.shape.attachedRigidBody; + } + + /** + * !#en + * get collider shape. + * !#zh + * 获取碰撞器形状。 + * @property {IBaseShape} shape + * @readonly + */ + public get shape () { + return this._shape; + } + + /// PRIVATE PROPERTY /// + + protected _shape!: IBaseShape; + + protected _isSharedMaterial: boolean = true; + + @property({ type: PhysicsMaterial }) + protected _material: PhysicsMaterial | null = null; + + @property + protected _isTrigger: boolean = false; + + @property + protected readonly _center: cc.Vec3 = new Vec3(); + + protected get _assertOnload (): boolean { + const r = this._isOnLoadCalled == 0; + if (r) { cc.error('Physics Error: Please make sure that the node has been added to the scene'); } + return !r; + } + + protected constructor () { + super() + cc.EventTarget.call(this); + } + + /// EVENT INTERFACE /// + + /** + * !#en + * Register an callback of a specific event type on the EventTarget. + * This type of event should be triggered via `emit`. + * !#zh + * 注册事件目标的特定事件类型回调。这种类型的事件应该被 `emit` 触发。 + * + * @method on + * @param {String} type - The type of collider event can be `trigger-enter`, `trigger-stay`, `trigger-exit` or `collision-enter`, `collision-stay`, `collision-exit`. + * @param {Function} callback - The callback that will be invoked when the event is dispatched. + * The callback is ignored if it is a duplicate (the callbacks are unique). + * @param {ITriggerEvent|ICollisionEvent} callback.event Callback function argument + * @param {Object} [target] - The target (this object) to invoke the callback, can be null. + * @return {Function} - Just returns the incoming callback so you can save the anonymous function easier. + * @typescript + * on(type: string, callback: T, target?: any, useCapture?: boolean): T + * @example + * eventTarget.on('fire', function (event) { + * // event is ITriggerEvent or ICollisionEvent + * }, node); + */ + public on (type: TriggerEventType | CollisionEventType, callback: TriggerCallback | CollisionCallback, target?: Object, useCapture?: any): any { + } + + /** + * !#en + * Removes the listeners previously registered with the same type, callback, target and or useCapture, + * if only type is passed as parameter, all listeners registered with that type will be removed. + * !#zh + * 删除之前用同类型,回调,目标或 useCapture 注册的事件监听器,如果只传递 type,将会删除 type 类型的所有事件监听器。 + * + * @method off + * @param {String} type - The type of collider event can be `trigger-enter`, `trigger-stay`, `trigger-exit` or `collision-enter`, `collision-stay`, `collision-exit`. + * @param {Function} [callback] - The callback to remove. + * @param {Object} [target] - The target (this object) to invoke the callback, if it's not given, only callback without target will be removed. + * @example + * // register fire eventListener + * var callback = eventTarget.on('fire', function () { + * cc.log("fire in the hole"); + * }, target); + * // remove fire event listener + * eventTarget.off('fire', callback, target); + * // remove all fire event listeners + * eventTarget.off('fire'); + */ + public off (type: TriggerEventType | CollisionEventType, callback: TriggerCallback | CollisionCallback, target?: any) { + } + + /** + * !#en + * Register an callback of a specific event type on the EventTarget, + * the callback will remove itself after the first time it is triggered. + * !#zh + * 注册事件目标的特定事件类型回调,回调会在第一时间被触发后删除自身。 + * + * @method once + * @param {String} type - The type of collider event can be `trigger-enter`, `trigger-stay`, `trigger-exit` or `collision-enter`, `collision-stay`, `collision-exit`. + * @param {Function} callback - The callback that will be invoked when the event is dispatched. + * The callback is ignored if it is a duplicate (the callbacks are unique). + * @param {ITriggerEvent|ICollisionEvent} callback.event callback function argument. + * @param {Object} [target] - The target (this object) to invoke the callback, can be null. + * @example + * eventTarget.once('fire', function (event) { + * // event is ITriggerEvent or ICollisionEvent + * }, node); + */ + public once (type: TriggerEventType | CollisionEventType, callback: TriggerCallback | CollisionCallback, target?: Object) { + } + + /* declare for typescript tip */ + public emit (key: TriggerEventType | CollisionEventType, ...args: any[]): void { + } + + /// COMPONENT LIFECYCLE /// + + protected __preload () { + if (!CC_EDITOR) { + this._shape.__preload!(this); + } + } + + protected onLoad () { + if (!CC_EDITOR) { + if (!CC_PHYSICS_BUILTIN) { + this.sharedMaterial = this._material == null ? cc.director.getPhysics3DManager().defaultMaterial : this._material; + } + this._shape.onLoad!(); + } + } + + protected onEnable () { + if (!CC_EDITOR) { + this._shape.onEnable!(); + } + } + + protected onDisable () { + if (!CC_EDITOR) { + this._shape.onDisable!(); + } + } + + protected onDestroy () { + if (!CC_EDITOR) { + if (this._material) { + this._material.off('physics_material_update', this._updateMaterial, this); + } + this._shape.onDestroy!(); + } + } + + private _updateMaterial () { + if (!CC_EDITOR) { + this._shape.material = this._material; + } + } + +} + +cc.js.mixin(Collider3D.prototype, cc.EventTarget.prototype); \ No newline at end of file diff --git a/cocos2d/core/3d/physics/framework/components/collider/sphere-collider-component.ts b/cocos2d/core/3d/physics/framework/components/collider/sphere-collider-component.ts new file mode 100644 index 00000000000..442b80d8e47 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/components/collider/sphere-collider-component.ts @@ -0,0 +1,91 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { createSphereShape } from '../../instance'; +import { Collider3D } from './collider-component'; +import { ISphereShape } from '../../../spec/i-physics-shape'; + +const { + ccclass, + executeInEditMode, + executionOrder, + menu, + property, +} = cc._decorator; + +/** + * !#en + * Physics sphere collider + * !#zh + * 物理球碰撞器 + * @class SphereCollider3D + * @extends Collider3D + */ +@ccclass('cc.SphereCollider3D') +@executionOrder(98) +@menu('i18n:MAIN_MENU.component.physics/Collider/Sphere 3D') +@executeInEditMode +export class SphereCollider3D extends Collider3D { + + /// PUBLIC PROPERTY GETTER\SETTER /// + + /** + * !#en + * Get or set the radius of the sphere. + * !#zh + * 获取或设置球的半径。 + * @property {number} radius + */ + @property + public get radius () { + return this._radius; + } + + public set radius (value) { + this._radius = value; + if (!CC_EDITOR) { + this.sphereShape.radius = this._radius; + } + } + + /** + * @property {ISphereShape} sphereShape + */ + public get sphereShape (): ISphereShape { + return this._shape as ISphereShape; + } + + /// PRIVATE PROPERTY /// + + @property + private _radius: number = 0.5; + + constructor () { + super(); + if (!CC_EDITOR) { + this._shape = createSphereShape(this._radius); + } + } +} diff --git a/cocos2d/core/3d/physics/framework/components/constant-force.ts b/cocos2d/core/3d/physics/framework/components/constant-force.ts new file mode 100644 index 00000000000..9b82cf7b861 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/components/constant-force.ts @@ -0,0 +1,188 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { RigidBody3D } from './rigid-body-component'; + +const { + ccclass, + executeInEditMode, + executionOrder, + menu, + property, + requireComponent, + disallowMultiple, +} = cc._decorator; +const Vec3 = cc.Vec3; + +/** + * !#en + * Each frame applies a constant force to a rigid body, depending on the RigidBody3D + * !#zh + * 在每帧对一个刚体施加持续的力,依赖 RigidBody3D 组件 + * @class ConstantForce + * @extends Component + */ +@ccclass('cc.ConstantForce') +@executionOrder(98) +@requireComponent(RigidBody3D) +@menu('i18n:MAIN_MENU.component.physics/Constant Force 3D') +@disallowMultiple +@executeInEditMode +export class ConstantForce extends cc.Component { + + private _rigidbody: RigidBody3D | null = null; + + @property + private readonly _force: cc.Vec3 = new Vec3(); + + @property + private readonly _localForce: cc.Vec3 = new Vec3(); + + @property + private readonly _torque: cc.Vec3 = new Vec3(); + + @property + private readonly _localTorque: cc.Vec3 = new Vec3(); + + private _mask: number = 0; + + /** + * !#en + * Set the force used in the world coordinate system, use `this.force = otherVec3`. + * !#zh + * 设置世界坐标系中使用的力,设置时请用 `this.force = otherVec3` 的方式。 + * @property {Vec3} force + */ + @property({ + displayOrder: 0 + }) + public get force () { + return this._force; + } + + public set force (value: cc.Vec3) { + Vec3.copy(this._force, value); + this._maskUpdate(this._force, 1); + } + + /** + * !#en + * Set the force used in the local coordinate system, using `this.localforce = otherVec3`. + * !#zh + * 获取和设置本地坐标系中使用的力,设置时请用 `this.localForce = otherVec3` 的方式。 + * @property {Vec3} localForce + */ + @property({ + displayOrder: 1 + }) + public get localForce () { + return this._localForce; + } + + public set localForce (value: cc.Vec3) { + Vec3.copy(this._localForce, value); + this._maskUpdate(this.localForce, 2); + } + + /** + * !#en + * Torque applied to the world orientation + * !#zh + * 对世界朝向施加的扭矩 + * @note + * 设置时请用 this.torque = otherVec3 的方式 + * @property {Vec3} torque + */ + @property({ + displayOrder: 2 + }) + public get torque () { + return this._torque; + } + + public set torque (value: cc.Vec3) { + Vec3.copy(this._torque, value); + this._maskUpdate(this._torque, 4); + } + + /** + * !#en + * Torque applied to local orientation, using `this.localtorque = otherVec3`. + * !#zh + * 对本地朝向施加的扭矩,设置时请用 `this.localTorque = otherVec3` 的方式。 + * @property {Vec3} localTorque + */ + @property({ + displayOrder: 3 + }) + public get localTorque () { + return this._localTorque; + } + + public set localTorque (value: cc.Vec3) { + Vec3.copy(this._localTorque, value); + this._maskUpdate(this._localTorque, 8); + } + + public onLoad () { + if (!CC_PHYSICS_BUILTIN) { + this._rigidbody = this.node.getComponent(RigidBody3D); + this._maskUpdate(this._force, 1); + this._maskUpdate(this._localForce, 2); + this._maskUpdate(this._torque, 4); + this._maskUpdate(this._localTorque, 8); + } + } + + public lateUpdate (dt: number) { + if (!CC_PHYSICS_BUILTIN) { + if (this._rigidbody != null && this._mask != 0) { + if (this._mask & 1) { + this._rigidbody.applyForce(this._force); + } + + if (this._mask & 2) { + this._rigidbody.applyLocalForce(this.localForce); + } + + if (this._mask & 4) { + this._rigidbody.applyTorque(this._torque); + } + + if (this._mask & 8) { + this._rigidbody.applyLocalTorque(this._localTorque); + } + } + } + } + + private _maskUpdate (t: cc.Vec3, m: number) { + if (Vec3.strictEquals(t, Vec3.ZERO)) { + this._mask &= ~m; + } else { + this._mask |= m; + } + } +} diff --git a/cocos2d/core/3d/physics/framework/components/rigid-body-component.ts b/cocos2d/core/3d/physics/framework/components/rigid-body-component.ts new file mode 100644 index 00000000000..d82d1c6ec43 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/components/rigid-body-component.ts @@ -0,0 +1,540 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { IRigidBody } from '../../spec/I-rigid-body'; +import { createRigidBody } from '../instance'; + +const { + ccclass, + disallowMultiple, + executeInEditMode, + executionOrder, + menu, + property, +} = cc._decorator; +const Vec3 = cc.Vec3; + +/** + * !#en + * RigidBody is the basic object that make up the physical world, and it can make a node physically affected and react. + * !#zh + * 刚体是组成物理世界的基本对象,可以让一个节点受到物理影响并产生反应。该组件在使用 Builtin 物理引擎时无效。 + * @class RigidBody3D + * @extends Component + */ +@ccclass('cc.RigidBody3D') +@executionOrder(99) +@menu('i18n:MAIN_MENU.component.physics/Rigid Body 3D') +@executeInEditMode +@disallowMultiple +export class RigidBody3D extends cc.Component { + + /// PUBLIC PROPERTY GETTER\SETTER /// + + /** + * !#en + * Whether sleep is allowed. + * !#zh + * 是否允许休眠。 + * @property {boolean} allowSleep + */ + public get allowSleep (): boolean { + return this._allowSleep; + } + + public set allowSleep (v: boolean) { + this._allowSleep = v; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.allowSleep = v; + } + } + + /** + * !#en + * The mass of the rigidbody. + * !#zh + * 刚体的质量。 + * @property {number} mass + */ + @property({ + displayOrder: 0 + }) + public get mass () { + return this._mass; + } + + public set mass (value) { + this._mass = value; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.mass = value; + } + } + + /** + * !#en + * Used to reduce the linear rate of rigidbody. The larger the value, the slower the rigidbody moves. + * !#zh + * 线性阻尼,用于减小刚体的线性速率,值越大物体移动越慢。 + * @property {number} linearDamping + */ + @property({ + displayOrder: 1 + }) + public get linearDamping () { + return this._linearDamping; + } + + public set linearDamping (value) { + this._linearDamping = value; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.linearDamping = value; + } + } + + /** + * !#en + * Used to reduce the rotation rate of rigidbody. The larger the value, the slower the rigidbody rotates. + * !#zh + * 角阻尼,用于减小刚体的旋转速率,值越大刚体旋转越慢。 + * @property {number} angularDamping + */ + @property({ + displayOrder: 2 + }) + public get angularDamping () { + return this._angularDamping; + } + + public set angularDamping (value) { + this._angularDamping = value; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.angularDamping = value; + } + } + + /** + * !#en + * If enabled, the developer controls the displacement and rotation of the rigidbody, not the physics engine. + * !#zh + * 是否由开发者来控制刚体的位移和旋转,而不是受物理引擎的影响。 + * @property {boolean} isKinematic + */ + @property({ + displayOrder: 3 + }) + public get isKinematic () { + return this._isKinematic; + } + + public set isKinematic (value) { + this._isKinematic = value; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.isKinematic = value; + } + } + + /** + * !#en + * If enabled, the rigidbody is affected by gravity. + * !#zh + * 如果开启,刚体会受到重力影响。 + * @property {boolean} useGravity + */ + @property({ + displayOrder: 4 + }) + public get useGravity () { + return this._useGravity; + } + + public set useGravity (value) { + this._useGravity = value; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.useGravity = value; + } + } + + /** + * !#en + * If enabled, the rigidbody will be fixed without rotation during a collision. + * !#zh + * 如果开启,发生碰撞时会固定刚体不产生旋转。 + * @property {boolean} fixedRotation + */ + @property({ + displayOrder: 5 + }) + public get fixedRotation () { + return this._fixedRotation; + } + + public set fixedRotation (value) { + this._fixedRotation = value; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.fixedRotation = value; + } + } + + /** + * !#en + * It can affect the linear velocity change of the rigidbody in each axis. The larger the value, the faster the rigidbody moves. + * !#zh + * 线性因子,可影响刚体在每个轴向的线性速度变化,值越大刚体移动越快。 + * @property {Vec3} linearFactor + */ + @property({ + displayOrder: 6 + }) + public get linearFactor (): cc.Vec3 { + return this._linearFactor; + } + + public set linearFactor (value: cc.Vec3) { + Vec3.copy(this._linearFactor, value); + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.linearFactor = this._linearFactor; + } + } + + /** + * !#en + * It can affect the rotation speed change of the rigidbody in each axis. The larger the value, the faster the rigidbody rotates. + * !#zh + * 旋转因子,可影响刚体在每个轴向的旋转速度变化,值越大刚体旋转越快。 + * @property {Vec3} angularFactor + */ + @property({ + displayOrder: 7 + }) + public get angularFactor () { + return this._angularFactor; + } + + public set angularFactor (value: cc.Vec3) { + Vec3.copy(this._angularFactor, value); + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.angularFactor = this._angularFactor; + } + } + + /** + * !#en + * The rigidbody is awake. + * !#zh + * 刚体是否为唤醒的状态。 + * @property {boolean} isAwake + * @readonly + */ + public get isAwake (): boolean { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + return this._body.isAwake; + } + return false; + } + + /** + * !#en + * The rigidbody can enter hibernation. + * !#zh + * 刚体是否为可进入休眠的状态。 + * @property {boolean} isSleepy + * @readonly + */ + public get isSleepy (): boolean { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + return this._body.isSleepy; + } + return false; + } + + /** + * !#en + * The rigidbody is sleeping. + * !#zh + * 刚体是否为正在休眠的状态。 + * @property {boolean} isSleeping + * @readonly + */ + public get isSleeping (): boolean { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + return this._body.isSleeping; + } + return false; + } + + /** + * !#en + * Get the rigidbody object inside the physics engine. + * !#zh + * 获得物理引擎内部刚体对象。 + * @property {IRigidBody} rigidBody + * @readonly + */ + public get rigidBody () { + return this._body; + } + + private _body!: IRigidBody; + + /// PRIVATE PROPERTY /// + + // @property + private _allowSleep: boolean = true; + + @property + private _mass: number = 10; + + @property + private _linearDamping: number = 0.1; + + @property + private _angularDamping: number = 0.1; + + @property + private _fixedRotation: boolean = false; + + @property + private _isKinematic: boolean = false; + + @property + private _useGravity: boolean = true; + + @property + private _linearFactor: cc.Vec3 = new Vec3(1, 1, 1); + + @property + private _angularFactor: cc.Vec3 = new Vec3(1, 1, 1); + + protected get _assertOnload (): boolean { + const r = this._isOnLoadCalled == 0; + if (r) { cc.error('Physics Error: Please make sure that the node has been added to the scene'); } + return !r; + } + + constructor () { + super(); + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body = createRigidBody(); + } + } + + /// COMPONENT LIFECYCLE /// + + protected __preload () { + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.__preload!(this); + } + } + + protected onEnable () { + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.onEnable!(); + } + } + + protected onDisable () { + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.onDisable!(); + } + } + + protected onDestroy () { + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.onDestroy!(); + } + } + + /// PUBLIC METHOD /// + + /** + * !#en + * A force is applied to a rigid body at a point in world space. + * !#zh + * 在世界空间中的某点上对刚体施加一个作用力。 + * @method applyForce + * @param {Vec3} force + * @param {Vec3} relativePoint The point of action, relative to the center of the rigid body. + */ + public applyForce (force: cc.Vec3, relativePoint?: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.applyForce(force, relativePoint); + } + } + + /** + * !#en + * Apply a force on the rigid body at a point in local space. + * !#zh + * 在本地空间中的某点上对刚体施加一个作用力。 + * @method applyLocalForce + * @param {Vec3} force + * @param {Vec3} localPoint Point of application + */ + public applyLocalForce (force: cc.Vec3, localPoint?: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.applyLocalForce(force, localPoint); + } + } + + /** + * !#en + * Apply an impulse to a rigid body at a point in world space. + * !#zh + * 在世界空间的某点上对刚体施加一个冲量。 + * @method applyImpulse + * @param {Vec3} impulse + * @param {Vec3} relativePoint The point of action, relative to the center of the rigid body. + */ + public applyImpulse (impulse: cc.Vec3, relativePoint?: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.applyImpulse(impulse, relativePoint); + } + } + + /** + * !#en + * Apply an impulse to the rigid body at a point in local space. + * !#zh + * 在本地空间的某点上对刚体施加一个冲量。 + * @method applyLocalImpulse + * @param {Vec3} impulse + * @param {Vec3} localPoint Point of application + */ + public applyLocalImpulse (impulse: cc.Vec3, localPoint?: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.applyLocalImpulse(impulse, localPoint); + } + } + + /** + * !#en + * Apply a torque to the rigid body. + * !#zh + * 对刚体施加扭转力。 + * @method applyTorque + * @param {Vec3} torque + */ + public applyTorque (torque: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.applyTorque(torque); + } + } + + /** + * !#en + * Apply a local torque to the rigid body. + * !#zh + * 对刚体施加本地扭转力。 + * @method applyLocalTorque + * @param {Vec3} torque + */ + public applyLocalTorque (torque: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.applyLocalTorque(torque); + } + } + + /** + * !#en + * Awaken the rigid body. + * !#zh + * 唤醒刚体。 + * @method wakeUp + */ + public wakeUp () { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.wakeUp(); + } + } + + /** + * !#en + * Dormant rigid body. + * !#zh + * 休眠刚体。 + * @method sleep + */ + public sleep () { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.sleep(); + } + } + + /** + * !#en + * Get linear velocity. + * !#zh + * 获取线性速度。 + * @method getLinearVelocity + * @param {Vec3} out + */ + public getLinearVelocity (out: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.getLinearVelocity(out); + } + } + + /** + * !#en + * Set linear speed. + * !#zh + * 设置线性速度。 + * @method setLinearVelocity + * @param {Vec3} value + */ + public setLinearVelocity (value: cc.Vec3): void { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.setLinearVelocity(value); + } + } + + /** + * !#en + * Gets the rotation speed. + * !#zh + * 获取旋转速度。 + * @method getAngularVelocity + * @param {Vec3} out + */ + public getAngularVelocity (out: cc.Vec3) { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.getAngularVelocity(out); + } + } + + /** + * !#en + * Set rotation speed. + * !#zh + * 设置旋转速度。 + * @method setAngularVelocity + * @param {Vec3} value + */ + public setAngularVelocity (value: cc.Vec3): void { + if (this._assertOnload && !CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this._body.setAngularVelocity(value); + } + } +} diff --git a/cocos2d/core/3d/physics/framework/index.ts b/cocos2d/core/3d/physics/framework/index.ts new file mode 100644 index 00000000000..adfe676572a --- /dev/null +++ b/cocos2d/core/3d/physics/framework/index.ts @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { Physics3DManager } from './physics-manager'; +import { PhysicsRayResult } from './physics-ray-result'; +import { BoxCollider3D } from './components/collider/box-collider-component'; +import { Collider3D } from './components/collider/collider-component'; +import { SphereCollider3D } from './components/collider/sphere-collider-component'; +import { RigidBody3D } from './components/rigid-body-component'; +import { ConstantForce } from './components/constant-force'; +import { PhysicsMaterial } from './assets/physics-material'; + +export { + Physics3DManager, + PhysicsRayResult, + PhysicsMaterial, + + Collider3D, + BoxCollider3D, + SphereCollider3D, + RigidBody3D, +}; + +cc.Physics3DManager = Physics3DManager; +cc.Collider3D = Collider3D; +cc.BoxCollider3D = BoxCollider3D; +cc.SphereCollider3D = SphereCollider3D; +cc.RigidBody3D = RigidBody3D; +cc.PhysicsRayResult = PhysicsRayResult; +cc.ConstantForce = ConstantForce; diff --git a/cocos2d/core/3d/physics/framework/instance.ts b/cocos2d/core/3d/physics/framework/instance.ts new file mode 100644 index 00000000000..4d16904f82e --- /dev/null +++ b/cocos2d/core/3d/physics/framework/instance.ts @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { BoxShape, PhysicsWorld, RigidBody, SphereShape } from './physics-selector'; +import { IRigidBody } from '../spec/I-rigid-body'; +import { IBoxShape, ISphereShape } from '../spec/i-physics-shape'; +import { IPhysicsWorld } from '../spec/i-physics-world'; + +export function createPhysicsWorld (): IPhysicsWorld { + return new PhysicsWorld() as IPhysicsWorld; +} + +export function createRigidBody (): IRigidBody { + return new RigidBody!() as IRigidBody; +} + +export function createBoxShape (size: cc.Vec3): IBoxShape { + return new BoxShape(size) as IBoxShape; +} + +export function createSphereShape (radius: number): ISphereShape { + return new SphereShape(radius) as ISphereShape; +} diff --git a/cocos2d/core/3d/physics/framework/physics-enum.ts b/cocos2d/core/3d/physics/framework/physics-enum.ts new file mode 100644 index 00000000000..b5b985cebca --- /dev/null +++ b/cocos2d/core/3d/physics/framework/physics-enum.ts @@ -0,0 +1,44 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + + /** + * !#en The rigid body type + * !#zh 刚体类型 + * @enum ERigidBodyType + */ +export enum ERigidBodyType { + /** + * @property {Number} DYNAMIC + */ + DYNAMIC = 1, + /** + * @property {Number} STATIC + */ + STATIC = 2, + /** + * @property {Number} KINEMATIC + */ + KINEMATIC = 4, +} diff --git a/cocos2d/core/3d/physics/framework/physics-interface.ts b/cocos2d/core/3d/physics/framework/physics-interface.ts new file mode 100644 index 00000000000..197e81e5709 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/physics-interface.ts @@ -0,0 +1,185 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { Collider3D } from './components/collider/collider-component'; + +/** + * !#en + * Trigger event + * !#zh + * 触发事件。 + * @class ITriggerEvent + */ +export interface ITriggerEvent { + /** + * !#en + * The type of event fired + * !#zh + * 触发的事件类型 + * @property {String} type + * @readonly + */ + readonly type: TriggerEventType; + + /** + * !#en + * Triggers its own collider in the event + * !#zh + * 触发事件中的自己的碰撞器 + * @property {Collider3D} selfCollider + * @readonly + */ + readonly selfCollider: Collider3D; + + /** + * !#en + * Triggers another collider in the event + * !#zh + * 触发事件中的另一个碰撞器 + * @property {Collider3D} otherCollider + * @readonly + */ + readonly otherCollider: Collider3D; +} + +/** + * !#en + * The value type definition of the trigger event. + * !#zh + * 触发事件的值类型定义。 + */ +export type TriggerEventType = 'trigger-enter' | 'trigger-stay' | 'trigger-exit'; + +/** + * !#en + * The callback signature definition of the event that was fired. + * !#zh + * 触发事件的回调函数签名定义。 + */ +export type TriggerCallback = (event: ITriggerEvent) => void; + +/** + * !#en + * Collision information for collision events. + * !#zh + * 碰撞事件的碰撞信息。 + * @class IContactEquation + */ +export interface IContactEquation { + /** + * !#en + * The collision point A in the collision information. + * !#zh + * 碰撞信息中的碰撞点 A。 + * @property {Vec3} contactA + * @readonly + */ + readonly contactA: cc.Vec3; + + /** + * !#en + * Collision point B in collision information. + * !#zh + * 碰撞信息中的碰撞点 B。 + * @property {Vec3} contactB + * @readonly + */ + readonly contactB: cc.Vec3; + + /** + * !#en + * Normals in collision information. + * !#zh + * 碰撞信息中的法线。 + * @property {Vec3} normal + * @readonly + */ + readonly normal: cc.Vec3; +} + +/** + * !#en + * Collision events. + * !#zh + * 碰撞事件。 + * @class ICollisionEvent + */ +export interface ICollisionEvent { + /** + * !#en + * Event type of collision. + * !#zh + * 碰撞的事件类型。 + * @property {String} type + * @readonly + */ + readonly type: CollisionEventType; + + /** + * !#en + * Collider of its own in collision. + * !#zh + * 碰撞中的自己的碰撞器。 + * @property {Collider3D} selfCollider + * @readonly + */ + readonly selfCollider: Collider3D; + + /** + * !#en + * Another collider in the collision. + * !#zh + * 碰撞中的另一个碰撞器。 + * @property {Collider3D} otherCollider + * @readonly + */ + readonly otherCollider: Collider3D; + + /** + * !#en + * Information about all the points of impact in the collision. + * !#zh + * 碰撞中的所有碰撞点的信息。 + * @property {IContactEquation[]} contacts + * @readonly + */ + readonly contacts: IContactEquation[]; +} + +/** + * !#en + * Value type definition for collision events. + * !#zh + * 碰撞事件的值类型定义。 + */ +export type CollisionEventType = 'collision-enter' | 'collision-stay' | 'collision-exit'; + +/** + * !#en + * The callback signature definition for the collision event. + * !#zh + * 碰撞事件的回调函数签名定义。 + */ +export type CollisionCallback = (event: ICollisionEvent) => void; diff --git a/cocos2d/core/3d/physics/framework/physics-manager.ts b/cocos2d/core/3d/physics/framework/physics-manager.ts new file mode 100644 index 00000000000..5fb40abfad8 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/physics-manager.ts @@ -0,0 +1,313 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { IPhysicsWorld, IRaycastOptions } from '../spec/i-physics-world'; +import { createPhysicsWorld } from './instance'; +import { PhysicsMaterial } from './assets/physics-material'; +import { PhysicsRayResult } from './physics-ray-result'; + +const { property, ccclass } = cc._decorator; + +/** + * !#en + * Physical systems manager. + * !#zh + * 物理系统管理器。 + * @class Physics3DManager + */ +@ccclass("cc.Physics3DManager") +export class Physics3DManager { + + /** + * !#en + * Whether to enable the physics system, default is false. + * !#zh + * 是否启用物理系统,默认不启用。 + * @property {boolean} enabled + */ + get enabled (): boolean { + return this._enabled; + } + set enabled (value: boolean) { + this._enabled = value; + } + + /** + * !#en + * Whether to allow the physics system to automatically hibernate, default is true. + * !#zh + * 物理系统是否允许自动休眠,默认为 true。 + * @property {boolean} allowSleep + */ + get allowSleep (): boolean { + return this._allowSleep; + } + set allowSleep (v: boolean) { + this._allowSleep = v; + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this.physicsWorld.allowSleep = this._allowSleep; + } + } + + /** + * !#en + * The maximum number of sub-steps a full step is permitted to be broken into, default is 2. + * !#zh + * 物理每帧模拟的最大子步数,默认为 2。 + * @property {number} maxSubStep + */ + get maxSubStep (): number { + return this._maxSubStep; + } + set maxSubStep (value: number) { + this._maxSubStep = value; + } + + /** + * !#en + * Time spent in each simulation of physics, default is 1/60s. + * !#zh + * 物理每步模拟消耗的固定时间,默认为 1/60 秒。 + * @property {number} deltaTime + */ + get deltaTime (): number { + return this._fixedTime; + } + set deltaTime (value: number) { + this._fixedTime = value; + } + + /** + * !#en + * Whether to use a fixed time step. + * !#zh + * 是否使用固定的时间步长。 + * @property {boolean} useFixedTime + */ + get useFixedTime (): boolean { + return this._useFixedTime; + } + set useFixedTime (value: boolean) { + this._useFixedTime = value; + } + + /** + * !#en + * Gravity value of the physics simulation, default is (0, -10, 0). + * !#zh + * 物理世界的重力数值,默认为 (0, -10, 0)。 + * @property {Vec3} gravity + */ + get gravity (): cc.Vec3 { + return this._gravity; + } + set gravity (gravity: cc.Vec3) { + this._gravity.set(gravity); + if (!CC_EDITOR && !CC_PHYSICS_BUILTIN) { + this.physicsWorld.gravity = gravity; + } + } + + /** + * !#en + * Gets the global default physical material. Note that builtin is null. + * !#zh + * 获取全局的默认物理材质,注意:builtin 时为 null。 + * @property {PhysicsMaterial | null} defaultMaterial + * @readonly + */ + get defaultMaterial (): PhysicsMaterial | null { + return this._material; + } + + readonly physicsWorld: IPhysicsWorld; + readonly raycastClosestResult = new PhysicsRayResult(); + readonly raycastResults: PhysicsRayResult[] = []; + + @property + private _enabled = false; + + @property + private _allowSleep = true; + + @property + private readonly _gravity = new cc.Vec3(0, -10, 0); + + @property + private _maxSubStep = 1; + + @property + private _fixedTime = 1.0 / 60.0; + + @property + private _useFixedTime = true; + + useAccumulator = false; + private _accumulator = 0; + + useFixedDigit = false; + useInternalTime = false; + + readonly fixDigits = { + position: 5, + rotation: 12, + timeNow: 3, + } + private _deltaTime = 0; + private _lastTime = 0; + private readonly _material: cc.PhysicsMaterial | null = null; + + private readonly raycastOptions: IRaycastOptions = { + 'groupIndex': -1, + 'queryTrigger': true, + 'maxDistance': Infinity + } + + private readonly raycastResultPool = new cc.RecyclePool(() => { + return new PhysicsRayResult(); + }, 1); + + private constructor () { + cc.director._scheduler && cc.director._scheduler.enableForTarget(this); + this.physicsWorld = createPhysicsWorld(); + this._lastTime = performance.now(); + if (!CC_PHYSICS_BUILTIN) { + this.gravity = this._gravity; + this.allowSleep = this._allowSleep; + this._material = new PhysicsMaterial(); + this._material.friction = 0.1; + this._material.restitution = 0.1; + this._material.on('physics_material_update', this._updateMaterial, this); + this.physicsWorld.defaultMaterial = this._material; + } + } + + /** + * !#en + * A physical system simulation is performed once and will now be performed automatically once per frame. + * !#zh + * 执行一次物理系统的模拟,目前将在每帧自动执行一次。 + * @method update + * @param {number} deltaTime The time difference from the last execution is currently elapsed per frame + */ + update (deltaTime: number) { + if (CC_EDITOR) { + return; + } + if (!this._enabled) { + return; + } + + if (this.useInternalTime) { + var now = parseFloat(performance.now().toFixed(this.fixDigits.timeNow)); + this._deltaTime = now > this._lastTime ? (now - this._lastTime) / 1000 : 0; + this._lastTime = now; + } else { + this._deltaTime = deltaTime; + } + + cc.director.emit(cc.Director.EVENT_BEFORE_PHYSICS); + + if (CC_PHYSICS_BUILTIN) { + this.physicsWorld.step(this._fixedTime); + } else { + if (this._useFixedTime) { + this.physicsWorld.step(this._fixedTime); + } else { + if (this.useAccumulator) { + let i = 0; + this._accumulator += this._deltaTime; + while (i < this._maxSubStep && this._accumulator > this._fixedTime) { + this.physicsWorld.step(this._fixedTime); + this._accumulator -= this._fixedTime; + i++; + } + } else { + this.physicsWorld.step(this._fixedTime, this._deltaTime, this._maxSubStep); + } + } + } + + cc.director.emit(cc.Director.EVENT_AFTER_PHYSICS); + } + + /** + * !#en Detect all collision boxes and return all detected results, or null if none is detected. Note that the return value is taken from the object pool, so do not save the result reference or modify the result. + * !#zh 检测所有的碰撞盒,并返回所有被检测到的结果,若没有检测到,则返回空值。注意返回值是从对象池中取的,所以请不要保存结果引用或者修改结果。 + * @method raycast + * @param {Ray} worldRay A ray in world space + * @param {number|string} groupIndexOrName Collision group index or group name + * @param {number} maxDistance Maximum detection distance + * @param {boolean} queryTrigger Detect trigger or not + * @return {PhysicsRayResult[] | null} Detected result + */ + raycast (worldRay: cc.geomUtils.Ray, groupIndexOrName: number | string = 0, maxDistance = Infinity, queryTrigger = true): PhysicsRayResult[] | null { + this.raycastResultPool.reset(); + this.raycastResults.length = 0; + if (typeof groupIndexOrName == "string") { + let groupIndex = cc.game.groupList.indexOf(groupIndexOrName); + if (groupIndex == -1) groupIndex = 0; + this.raycastOptions.groupIndex = groupIndex; + } else { + this.raycastOptions.groupIndex = groupIndexOrName; + } + this.raycastOptions.maxDistance = maxDistance; + this.raycastOptions.queryTrigger = queryTrigger; + let result = this.physicsWorld.raycast(worldRay, this.raycastOptions, this.raycastResultPool, this.raycastResults); + if (result) return this.raycastResults; + return null; + } + + /** + * !#en Detect all collision boxes and return the detection result with the shortest ray distance. If not, return null value. Note that the return value is taken from the object pool, so do not save the result reference or modify the result. + * !#zh 检测所有的碰撞盒,并返回射线距离最短的检测结果,若没有,则返回空值。注意返回值是从对象池中取的,所以请不要保存结果引用或者修改结果。 + * @method raycastClosest + * @param {Ray} worldRay A ray in world space + * @param {number|string} groupIndexOrName Collision group index or group name + * @param {number} maxDistance Maximum detection distance + * @param {boolean} queryTrigger Detect trigger or not + * @return {PhysicsRayResult|null} Detected result + */ + raycastClosest (worldRay: cc.geomUtils.Ray, groupIndexOrName: number | string = 0, maxDistance = Infinity, queryTrigger = true): PhysicsRayResult | null { + if (typeof groupIndexOrName == "string") { + let groupIndex = cc.game.groupList.indexOf(groupIndexOrName); + if (groupIndex == -1) groupIndex = 0; + this.raycastOptions.groupIndex = groupIndex; + } else { + this.raycastOptions.groupIndex = groupIndexOrName; + } + this.raycastOptions.maxDistance = maxDistance; + this.raycastOptions.queryTrigger = queryTrigger; + let result = this.physicsWorld.raycastClosest(worldRay, this.raycastOptions, this.raycastClosestResult); + if (result) return this.raycastClosestResult; + return null; + } + + private _updateMaterial () { + if (!CC_PHYSICS_BUILTIN) { + this.physicsWorld.defaultMaterial = this._material; + } + } +} diff --git a/cocos2d/core/3d/physics/framework/physics-ray-result.ts b/cocos2d/core/3d/physics/framework/physics-ray-result.ts new file mode 100644 index 00000000000..f50a71f2396 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/physics-ray-result.ts @@ -0,0 +1,108 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { Collider3D } from '../exports/physics-framework'; +const Vec3 = cc.Vec3; + +/** + * !#en + * Used to store physical ray detection results + * !#zh + * 用于保存物理射线检测结果 + * @class PhysicsRayResult + */ +export class PhysicsRayResult { + + /** + * !#en + * Hit the point + * !#zh + * 击中点 + * @property {Vec3} hitPoint + * @readonly + */ + get hitPoint (): cc.Vec3 { + return this._hitPoint; + } + + /** + * !#en + * Distance + * !#zh + * 距离 + * @property {number} distance + * @readonly + */ + get distance (): number { + return this._distance; + } + + /** + * !#en + * Hit the collision box + * !#zh + * 击中的碰撞盒 + * @property {Collider3D} collider + * @readonly + */ + get collider (): Collider3D { + return this._collidier!; + } + + private _hitPoint: cc.Vec3 = new Vec3(); + private _distance: number = 0; + private _collidier: Collider3D | null = null; + + /** + * !#en + * Set up ray. This method is used internally by the engine. Do not call it from an external script + * !#zh + * 设置射线,此方法由引擎内部使用,请勿在外部脚本调用 + * @method _assign + * @param {Vec3} hitPoint + * @param {number} distance + * @param {Collider3D} collider + */ + public _assign (hitPoint: cc.Vec3, distance: number, collider: Collider3D) { + Vec3.copy(this._hitPoint, hitPoint); + this._distance = distance; + this._collidier = collider; + } + + /** + * !#en + * Clone + * !#zh + * 克隆 + * @method clone + */ + public clone () { + const c = new PhysicsRayResult(); + Vec3.copy(c._hitPoint, this._hitPoint); + c._distance = this._distance; + c._collidier = this._collidier; + return c; + } +} diff --git a/cocos2d/core/3d/physics/framework/physics-selector.ts b/cocos2d/core/3d/physics/framework/physics-selector.ts new file mode 100644 index 00000000000..99d69199391 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/physics-selector.ts @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + + // Cannon +import { CannonRigidBody } from '../cannon/cannon-rigid-body'; +import { CannonWorld } from '../cannon/cannon-world'; +import { CannonBoxShape } from '../cannon/shapes/cannon-box-shape'; +import { CannonSphereShape } from '../cannon/shapes/cannon-sphere-shape'; + +// built-in +import { BuiltInWorld } from '../cocos/builtin-world'; +import { BuiltinBoxShape } from '../cocos/shapes/builtin-box-shape'; +import { BuiltinSphereShape } from '../cocos/shapes/builtin-sphere-shape'; + +export let BoxShape: typeof CannonBoxShape | typeof BuiltinBoxShape; +export let SphereShape: typeof CannonSphereShape | typeof BuiltinSphereShape; +export let RigidBody: typeof CannonRigidBody | null; +export let PhysicsWorld: typeof CannonWorld | typeof BuiltInWorld; + +export function instantiate ( + boxShape: typeof BoxShape, + sphereShape: typeof SphereShape, + body: typeof RigidBody, + world: typeof PhysicsWorld) { + BoxShape = boxShape; + SphereShape = sphereShape; + RigidBody = body; + PhysicsWorld = world; +} diff --git a/cocos2d/core/3d/physics/framework/util.ts b/cocos2d/core/3d/physics/framework/util.ts new file mode 100644 index 00000000000..6e62a297077 --- /dev/null +++ b/cocos2d/core/3d/physics/framework/util.ts @@ -0,0 +1,183 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { IVec3Like, IQuatLike } from '../spec/i-common'; + +export function stringfyVec3 (value: IVec3Like): string { + return `(x: ${value.x}, y: ${value.y}, z: ${value.z})`; +} + +export function stringfyQuat (value: IQuatLike): string { + return `(x: ${value.x}, y: ${value.y}, z: ${value.z}, w: ${value.w})`; +} + +interface IWrapped { + __cc_wrapper__: T; +} + +export function setWrap (object: any, wrapper: Wrapper) { + (object as IWrapped).__cc_wrapper__ = wrapper; +} + +export function getWrap (object: any) { + return (object as IWrapped).__cc_wrapper__; +} + +const LocalDirtyFlag = cc.Node._LocalDirtyFlag; +const PHYSICS_TRS = LocalDirtyFlag.PHYSICS_TRS; +const ALL_TRS = LocalDirtyFlag.ALL_TRS; +const SKEW = LocalDirtyFlag.SKEW; +const FLAG_TRANSFORM = cc.RenderFlow.FLAG_TRANSFORM; + +const Mat3 = cc.Mat3; +const Mat4 = cc.Mat4; +const Vec3 = cc.Vec3; +const Quat = cc.Quat; +const Trs = cc.Trs; + +const _nodeArray: Array = []; +const _lpos = cc.v3(); +const _lrot = cc.quat(); +const _mat3 = new Mat3(); +const _mat3m = _mat3.m; +const _quat = cc.quat(); +const _mat4 = cc.mat4(); + +let _nodeTransformRecord = {}; +export function clearNodeTransformDirtyFlag () { + for (let key in _nodeTransformRecord) { + let physicsNode = _nodeTransformRecord[key]; + physicsNode._localMatDirty &= ~ALL_TRS; + if (!(physicsNode._localMatDirty & SKEW)) { + physicsNode._worldMatDirty = false; + !CC_NATIVERENDERER && (physicsNode._renderFlag &= ~FLAG_TRANSFORM); + } + } + _nodeTransformRecord = {}; + _nodeArray.length = 0; +} + +export function clearNodeTransformRecord () { + _nodeTransformRecord = {}; + _nodeArray.length = 0; +} + +/* + * The method of node backtrace is used to optimize the calculation of global transformation. + * Node backtrace is continuous until the parent node is empty or the parent node has performed the calculation of global transformation. + * The result of backtrace will store the node relational chain in the array. + * The process of traversing array is equivalent to the process of global transformation from the parent node to the physical node. + * The calculated results are saved in the node, and the physical global transformation flag will be erased finally. + */ +export function updateWorldTransform (node: cc.Node, traverseAllNode: boolean = false) { + let cur = node; + let i = 0; + let needUpdateTransform = false; + let physicsDirtyFlag = 0; + while (cur) { + // If current node transform has been calculated + if (traverseAllNode || !_nodeTransformRecord[cur._id]) { + _nodeArray[i++] = cur; + } else { + // Current node's transform has beed calculated + physicsDirtyFlag |= (cur._localMatDirty & PHYSICS_TRS); + needUpdateTransform = needUpdateTransform || !!physicsDirtyFlag; + break; + } + if (cur._localMatDirty & PHYSICS_TRS) { + needUpdateTransform = true; + } + cur = cur._parent; + } + if (!needUpdateTransform) { + return false; + } + + let child; + let childWorldMat, curWorldMat, childTrs, childLocalMat; + let wpos, wrot, wscale; + + _nodeArray.length = i; + while (i) { + child = _nodeArray[--i]; + !traverseAllNode && (_nodeTransformRecord[child._id] = child); + + childWorldMat = child._worldMatrix; + childLocalMat = child._matrix; + childTrs = child._trs; + + wpos = child.__wpos = child.__wpos || cc.v3(); + wrot = child.__wrot = child.__wrot || cc.quat(); + wscale = child.__wscale = child.__wscale || cc.v3(); + + if (child._localMatDirty & PHYSICS_TRS) { + Trs.toMat4(childLocalMat, childTrs); + } + child._localMatDirty |= physicsDirtyFlag; + physicsDirtyFlag |= (child._localMatDirty & PHYSICS_TRS); + + if (!(physicsDirtyFlag & PHYSICS_TRS)) { + cur = child; + continue; + } + + if (cur) { + curWorldMat = cur._worldMatrix; + Trs.toPosition(_lpos, childTrs); + Vec3.transformMat4(wpos, _lpos, curWorldMat); + + Mat4.multiply(childWorldMat, curWorldMat, childLocalMat); + Trs.toRotation(_lrot, childTrs); + Quat.multiply(wrot, cur.__wrot, _lrot); + + Mat3.fromQuat(_mat3, Quat.conjugate(_quat, wrot)); + Mat3.multiplyMat4(_mat3, _mat3, childWorldMat); + wscale.x = _mat3m[0]; + wscale.y = _mat3m[4]; + wscale.z = _mat3m[8]; + } else { + Trs.toPosition(wpos, childTrs); + Trs.toRotation(wrot, childTrs); + Trs.toScale(wscale, childTrs); + Mat4.copy(childWorldMat, childLocalMat); + } + cur = child; + } + return true; +} + +export function updateWorldRT (node: cc.Node, position: cc.Vec3, rotation: cc.Quat) { + let parent = node.parent; + if (parent) { + updateWorldTransform(parent, true); + Vec3.transformMat4(_lpos, position, Mat4.invert(_mat4, parent._worldMatrix)); + Quat.multiply(_quat, Quat.conjugate(_quat, parent.__wrot), rotation); + node.setPosition(_lpos); + node.setRotation(_quat); + } else { + node.setPosition(position); + node.setRotation(rotation); + } +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/spec/I-rigid-body.ts b/cocos2d/core/3d/physics/spec/I-rigid-body.ts new file mode 100644 index 00000000000..412fed678cb --- /dev/null +++ b/cocos2d/core/3d/physics/spec/I-rigid-body.ts @@ -0,0 +1,155 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { IVec3Like } from "../../../value-types/math"; +import { RigidBody3D } from '../framework/components/rigid-body-component'; + +/** + * Rigid body interface + * @class IRigidBody + */ +export interface IRigidBody { + /** + * @property {RigidBody3D} rigidBody + */ + rigidBody: RigidBody3D; + + /** + * @property {number} mass + */ + mass: number; + /** + * @property {number} linearDamping + */ + linearDamping: number; + /** + * @property {number} angularDamping + */ + angularDamping: number; + /** + * @property {boolean} isKinematic + */ + isKinematic: boolean; + /** + * @property {boolean} useGravity + */ + useGravity: boolean; + /** + * @property {boolean} fixedRotation + */ + fixedRotation: boolean; + /** + * @property {IVec3Like} linearFactor + */ + linearFactor: IVec3Like; + /** + * @property {IVec3Like} angularFactor + */ + angularFactor: IVec3Like; + /** + * @property {boolean} allowSleep + */ + allowSleep: boolean; + /** + * @property {boolean} isAwake + * @readonly + */ + readonly isAwake: boolean; + /** + * @property {boolean} isSleepy + * @readonly + */ + readonly isSleepy: boolean; + /** + * @property {boolean} isSleeping + * @readonly + */ + readonly isSleeping: boolean; + + /** + * @method wakeUp + */ + wakeUp (): void; + /** + * @method sleep + */ + sleep (): void; + + /** + * @method getLinearVelocity + * @param {IVec3Like} out + */ + getLinearVelocity (out: IVec3Like): void; + /** + * @method setLinearVelocity + * @param {IVec3Like} out + */ + setLinearVelocity (value: IVec3Like): void; + /** + * @method getAngularVelocity + * @param {IVec3Like} out + */ + getAngularVelocity (out: IVec3Like): void; + /** + * @method setAngularVelocity + * @param {IVec3Like} out + */ + setAngularVelocity (value: IVec3Like): void; + + /** + * @method applyForce + * @param {IVec3Like} force + * @param {IVec3Like} relativePoint + */ + applyForce (force: IVec3Like, relativePoint?: IVec3Like): void; + /** + * @method applyLocalForce + * @param {IVec3Like} force + * @param {IVec3Like} relativePoint + */ + applyLocalForce (force: IVec3Like, relativePoint?: IVec3Like): void; + /** + * @method applyImpulse + * @param {IVec3Like} force + * @param {IVec3Like} relativePoint + */ + applyImpulse (force: IVec3Like, relativePoint?: IVec3Like): void; + /** + * @method applyLocalImpulse + * @param {IVec3Like} force + * @param {IVec3Like} relativePoint + */ + applyLocalImpulse (force: IVec3Like, relativePoint?: IVec3Like): void; + /** + * @method applyTorque + * @param {IVec3Like} torque + */ + applyTorque (torque: IVec3Like): void; + /** + * @method applyLocalTorque + * @param {IVec3Like} torque + */ + applyLocalTorque (torque: IVec3Like): void; +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/spec/i-common.ts b/cocos2d/core/3d/physics/spec/i-common.ts new file mode 100644 index 00000000000..b05efb03815 --- /dev/null +++ b/cocos2d/core/3d/physics/spec/i-common.ts @@ -0,0 +1,66 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +/** + * Class has x y z properties + * @class IVec3Like + */ +export interface IVec3Like { + /** + * @property {number} x + */ + x: number; + /** + * @property {number} y + */ + y: number; + /** + * @property {number} z + */ + z: number; +} + +/** + * Class has x y z w properties + * @class IQuatLike + */ +export interface IQuatLike { + /** + * @property {number} x + */ + x: number; + /** + * @property {number} y + */ + y: number; + /** + * @property {number} z + */ + z: number; + /** + * @property {number} w + */ + w: number; +} \ No newline at end of file diff --git a/cocos2d/core/3d/physics/spec/i-physics-shape.ts b/cocos2d/core/3d/physics/spec/i-physics-shape.ts new file mode 100644 index 00000000000..9d2389d4612 --- /dev/null +++ b/cocos2d/core/3d/physics/spec/i-physics-shape.ts @@ -0,0 +1,77 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { IVec3Like } from "../../../value-types/math"; +import { Collider3D, RigidBody3D } from '../exports/physics-framework'; + +/** + * !#en Base shape interface. + * @class IBaseShape + */ +export interface IBaseShape { + /** + * @property {Collider3D} collider + */ + readonly collider: Collider3D; + /** + * @property {RigidBody3D | null} attachedRigidBody + */ + readonly attachedRigidBody: RigidBody3D | null; + /** + * @property {any} material + */ + material: any; + /** + * @property {boolean} isTrigger + */ + isTrigger: boolean; + /** + * @property {IVec3Like} center + */ + center: IVec3Like; +} + +/** + * !#en box shape interface + * @class IBoxShape + */ +export interface IBoxShape extends IBaseShape { + /** + * @property {IVec3Like} size + */ + size: IVec3Like; +} + +/** + * !#en Sphere shape interface + * @class ISphereShape + */ +export interface ISphereShape extends IBaseShape { + /** + * @property {number} radius + */ + radius: number; +} + diff --git a/cocos2d/core/3d/physics/spec/i-physics-world.ts b/cocos2d/core/3d/physics/spec/i-physics-world.ts new file mode 100644 index 00000000000..e51b445e430 --- /dev/null +++ b/cocos2d/core/3d/physics/spec/i-physics-world.ts @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { IVec3Like } from "../../../value-types/math"; +import { PhysicsRayResult } from '../framework/physics-ray-result'; + +/** + * Ray cast options + * @class IRaycastOptions + */ +export interface IRaycastOptions { + /** + * @property {number} groupIndex + */ + groupIndex: number; + /** + * @property {boolean} queryTrigger + */ + queryTrigger: boolean; + /** + * @property {number} maxDistance + */ + maxDistance: number; +} + +/** + * Collision detect + * @class ICollisionDetect + */ +export interface ICollisionDetect { + + step (deltaTime: number, ...args: any): void; + + /** + * Ray cast, and return information of the closest hit. + * @method raycastClosest + * @param {Ray} worldRay + * @param {IRaycastOptions} options + * @param {PhysicsRayResult} out + * @return {boolean} True if any body was hit. + */ + raycastClosest (worldRay: cc.geomUtils.Ray, options: IRaycastOptions, out: PhysicsRayResult): boolean; + + /** + * Ray cast against all bodies. The provided callback will be executed for each hit with a RaycastResult as single argument. + * @method raycast + * @param {Ray} worldRay + * @param {IRaycastOptions} options + * @param {RecyclePool} pool + * @param {PhysicsRayResult[]} resultes + * @return {boolean} True if any body was hit. + */ + raycast (worldRay: cc.geomUtils.Ray, options: IRaycastOptions, pool: cc.RecyclePool, resultes: PhysicsRayResult[]): boolean +} + +/** + * Physics world interface + * @class IPhysicsWorld + */ +export interface IPhysicsWorld extends ICollisionDetect { + gravity: IVec3Like; + allowSleep: boolean; + defaultMaterial: any; + syncSceneToPhysics?: () => void; + syncPhysicsToScene?: () => void; + emitEvents?: () => void; +} \ No newline at end of file diff --git a/cocos2d/core/3d/polyfill-3d.js b/cocos2d/core/3d/polyfill-3d.js deleted file mode 100644 index ef3ea6d32ca..00000000000 --- a/cocos2d/core/3d/polyfill-3d.js +++ /dev/null @@ -1,303 +0,0 @@ -/**************************************************************************** - Copyright (c) 2018 Xiamen Yaji Software Co., Ltd. - - http://www.cocos.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated engine source code (the "Software"), a limited, - worldwide, royalty-free, non-assignable, revocable and non-exclusive license - to use Cocos Creator solely to develop games on your target platforms. You shall - not use Cocos Creator software for developing other software or tools that's - used for developing games. You are not granted to publish, distribute, - sublicense, and/or sell copies of Cocos Creator. - - The software or tools in this License Agreement are licensed, not sold. - Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ - - -const Node = require('../CCNode'); -const EventType = Node.EventType; -const DirtyFlag = Node._LocalDirtyFlag; -const math = require('../renderer/render-engine').math; -const RenderFlow = require('../renderer/render-flow'); -const misc = require('../utils/misc'); - -// ====== Node transform polyfills ====== -const ONE_DEGREE = Math.PI / 180; - -const POSITION_ON = 1 << 0; -const SCALE_ON = 1 << 1; -const ROTATION_ON = 1 << 2; - -function _updateLocalMatrix3d () { - if (this._localMatDirty) { - // Update transform - let t = this._matrix; - math.mat4.fromRTS(t, this._quat, this._position, this._scale); - - // skew - if (this._skewX || this._skewY) { - let a = t.m00, b = t.m01, c = t.m04, d = t.m05; - let skx = Math.tan(this._skewX * ONE_DEGREE); - let sky = Math.tan(this._skewY * ONE_DEGREE); - if (skx === Infinity) - skx = 99999999; - if (sky === Infinity) - sky = 99999999; - t.m00 = a + c * sky; - t.m01 = b + d * sky; - t.m04 = c + a * skx; - t.m05 = d + b * skx; - } - this._localMatDirty = 0; - // Register dirty status of world matrix so that it can be recalculated - this._worldMatDirty = true; - } -} - -function _calculWorldMatrix3d () { - // Avoid as much function call as possible - if (this._localMatDirty) { - this._updateLocalMatrix(); - } - - if (this._parent) { - let parentMat = this._parent._worldMatrix; - math.mat4.mul(this._worldMatrix, parentMat, this._matrix); - } - else { - math.mat4.copy(this._worldMatrix, this._matrix); - } - this._worldMatDirty = false; -} - - - -/** - * !#en Returns a copy of the position (x, y, z) of the node in its parent's coordinates. - * !#zh 获取节点在父节点坐标系中的位置(x, y, z)。 - * @method getPosition - * @return {Vec3} The position (x, y, z) of the node in its parent's coordinates - */ -function getPosition () { - return new cc.Vec3(this._position); -} - -/** - * !#en - * Sets the position (x, y, z) of the node in its parent's coordinates.
- * Usually we use cc.v3(x, y, z) to compose cc.Vec3 object.
- * and Passing two numbers (x, y, z) is more efficient than passing cc.Vec3 object. - * !#zh - * 设置节点在父节点坐标系中的位置。
- * 可以通过两种方式设置坐标点:
- * 1. 传入 3 个数值 x, y, z。
- * 2. 传入 cc.v3(x, y, z) 类型为 cc.Vec3 的对象。 - * @method setPosition - * @param {Vec3|Number} newPosOrX - X coordinate for position or the position (x, y, z) of the node in coordinates - * @param {Number} [y] - Y coordinate for position - * @param {Number} [z] - Z coordinate for position - */ -function setPosition (newPosOrX, y, z) { - let x; - if (y === undefined) { - x = newPosOrX.x; - y = newPosOrX.y; - z = newPosOrX.z || 0; - } - else { - x = newPosOrX; - z = z || 0 - } - - let pos = this._position; - if (pos.x === x && pos.y === y && pos.z === z) { - return; - } - - if (CC_EDITOR) { - let oldPosition = new cc.Vec3(pos); - } - - pos.x = x; - pos.y = y; - pos.z = z; - this.setLocalDirty(DirtyFlag.POSITION); - this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM; - - // fast check event - if (this._eventMask & POSITION_ON) { - this.emit(EventType.POSITION_CHANGED); - } -} - -/** - * !#en Get rotation of node (in quaternion). - * !#zh 获取该节点的 quaternion 旋转角度。 - * @method getQuat - * @return {cc.Quat} Quaternion object represents the rotation - */ -function getQuat () { - return math.quat.clone(this._quat); -} - -/** - * !#en Set rotation of node (in quaternion). - * !#zh 设置该节点的 quaternion 旋转角度。 - * @method setQuat - * @param {cc.Quat|Number} quat Quaternion object represents the rotation or the x value of quaternion - * @param {Number} y y value of quternion - * @param {Number} z z value of quternion - * @param {Number} w w value of quternion - */ -function setQuat (quat, y, z, w) { - let x = quat; - if (y === undefined) { - x = quat.x; - y = quat.y; - z = quat.z; - w = quat.w; - } - - let old = this._quat; - if (old.x !== x || old.y !== y || old.z !== z || old.w !== w) { - old.x = x; - old.y = y; - old.z = z; - old.w = w; - this.setLocalDirty(DirtyFlag.ROTATION); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; - - if (this._eventMask & ROTATION_ON) { - this.emit(EventType.ROTATION_CHANGED); - } - } - - if (CC_EDITOR) { - this._syncEulerAngles(); - } -} - -/** - * !#en - * Returns the scale of the node. - * !#zh 获取节点的缩放。 - * @method getScale - * @return {cc.Vec3} The scale factor - */ -function getScale () { - return cc.v3(this._scale); -} - -/** - * !#en Sets the scale of three axis in local coordinates of the node. - * !#zh 设置节点在本地坐标系中三个坐标轴上的缩放比例。 - * @method setScale - * @param {Number|Vec3} x - scaleX or scale object - * @param {Number} [y] - * @param {Number} [z] - * @example - * node.setScale(cc.v2(2, 2, 2)); - * node.setScale(2); - */ -function setScale (x, y, z) { - if (x && typeof x !== 'number') { - y = x.y; - z = x.z || 1; - x = x.x; - } - else if (x !== undefined && y === undefined) { - y = x; - z = x; - } - else if (z === undefined) { - z = 1; - } - if (this._scale.x !== x || this._scale.y !== y || this._scale.z !== z) { - this._scale.x = x; - this._scale.y = y; - this._scale.z = z; - this.setLocalDirty(DirtyFlag.SCALE); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; - - if (this._eventMask & SCALE_ON) { - this.emit(EventType.SCALE_CHANGED); - } - } -} - -function _syncEulerAngles () { - this._rotationX = this._quat.getRoll(); - this._rotationY = this._quat.getPitch(); - this._rotationZ = this._quat.getYaw(); -} - -function _update3DFunction () { - if (this._is3DNode) { - this._updateLocalMatrix = _updateLocalMatrix3d; - this._calculWorldMatrix = _calculWorldMatrix3d; - this._mulMat = cc.vmath.mat4.mul; - this._syncEulerAngles = _syncEulerAngles; - } - else { - this._updateLocalMatrix = _updateLocalMatrix2d; - this._calculWorldMatrix = _calculWorldMatrix2d; - this._mulMat = _mulMat2d; - this._syncEulerAngles = _syncEulerAngles2d; - } -} - -function _upgrade_1x_to_2x () { - if (CC_EDITOR && this instanceof cc.Scene) { - this._is3DNode = true; - } - - if (this._is3DNode) { - this._update3DFunction(); - } - - _upgrade_1x_to_2x_2d.call(this); -} - - -let proto = cc.Node.prototype; -const _updateLocalMatrix2d = proto._updateLocalMatrix; -const _calculWorldMatrix2d = proto._calculWorldMatrix; -const _upgrade_1x_to_2x_2d = proto._upgrade_1x_to_2x; -const _syncEulerAngles2d = proto._syncEulerAngles; -const _mulMat2d = proto._mulMat; -const _onBatchCreated2d = proto._onBatchCreated; - -proto.getQuat = getQuat; -proto.setQuat = setQuat; - -proto.getPosition = getPosition; -proto.setPosition = setPosition; -proto.getScale = getScale; -proto.setScale = setScale; - -proto._upgrade_1x_to_2x = _upgrade_1x_to_2x; -proto._update3DFunction = _update3DFunction; - -cc.js.getset(proto, 'position', getPosition, setPosition, false, true); -cc.js.getset(proto, 'scale', getScale, setScale, false, true); - -cc.js.getset(proto, 'is3DNode', function () { - return this._is3DNode; -}, function (v) { - if (this._is3DNode === v) return; - this._is3DNode = v; - this._update3DFunction(); - this._syncEulerAngles(); -}); - -cc.js.getset(proto, 'quat', getQuat, setQuat); diff --git a/cocos2d/core/3d/primitive/box.ts b/cocos2d/core/3d/primitive/box.ts new file mode 100644 index 00000000000..9fc65877217 --- /dev/null +++ b/cocos2d/core/3d/primitive/box.ts @@ -0,0 +1,131 @@ +'use strict'; + +import Vec3 from '../../value-types/vec3'; +import VertexData from './vertex-data'; + +let temp1 = new Vec3(); +let temp2 = new Vec3(); +let temp3 = new Vec3(); +let r = new Vec3(); +let c0 = new Vec3(); +let c1 = new Vec3(); +let c2 = new Vec3(); +let c3 = new Vec3(); +let c4 = new Vec3(); +let c5 = new Vec3(); +let c6 = new Vec3(); +let c7 = new Vec3(); + +/** + * @param {Number} width + * @param {Number} height + * @param {Number} length + * @param {Object} opts + * @param {Number} opts.widthSegments + * @param {Number} opts.heightSegments + * @param {Number} opts.lengthSegments + */ +export default function (width = 1, height = 1, length = 1, opts = {widthSegments: 1, heightSegments: 1, lengthSegments: 1, invWinding: false}) { + let ws = opts.widthSegments; + let hs = opts.heightSegments; + let ls = opts.lengthSegments; + let inv = opts.invWinding; + + let hw = width * 0.5; + let hh = height * 0.5; + let hl = length * 0.5; + + let corners = [ + Vec3.set(c0, -hw, -hh, hl), + Vec3.set(c1, hw, -hh, hl), + Vec3.set(c2, hw, hh, hl), + Vec3.set(c3, -hw, hh, hl), + Vec3.set(c4, hw, -hh, -hl), + Vec3.set(c5, -hw, -hh, -hl), + Vec3.set(c6, -hw, hh, -hl), + Vec3.set(c7, hw, hh, -hl), + ]; + + let faceAxes = [ + [ 2, 3, 1 ], // FRONT + [ 4, 5, 7 ], // BACK + [ 7, 6, 2 ], // TOP + [ 1, 0, 4 ], // BOTTOM + [ 1, 4, 2 ], // RIGHT + [ 5, 0, 6 ] // LEFT + ]; + + let faceNormals = [ + [ 0, 0, 1 ], // FRONT + [ 0, 0, -1 ], // BACK + [ 0, 1, 0 ], // TOP + [ 0, -1, 0 ], // BOTTOM + [ 1, 0, 0 ], // RIGHT + [ -1, 0, 0 ] // LEFT + ]; + + let positions: number[] = []; + let normals: number[] = []; + let uvs: number[] = []; + let indices: number[] = []; + let minPos = new Vec3(-hw, -hh, -hl); + let maxPos = new Vec3(hw, hh, hl); + let boundingRadius = Math.sqrt(hw * hw + hh * hh + hl * hl); + + function _buildPlane (side, uSegments, vSegments) { + let u, v; + let ix, iy; + let offset = positions.length / 3; + let faceAxe = faceAxes[side]; + let faceNormal = faceNormals[side]; + + for (iy = 0; iy <= vSegments; iy++) { + for (ix = 0; ix <= uSegments; ix++) { + u = ix / uSegments; + v = iy / vSegments; + + Vec3.lerp(temp1, corners[faceAxe[0]], corners[faceAxe[1]], u); + Vec3.lerp(temp2, corners[faceAxe[0]], corners[faceAxe[2]], v); + Vec3.subtract(temp3, temp2, corners[faceAxe[0]]); + Vec3.add(r, temp1, temp3); + + positions.push(r.x, r.y, r.z); + normals.push(faceNormal[0], faceNormal[1], faceNormal[2]); + uvs.push(u, v); + + if ((ix < uSegments) && (iy < vSegments)) { + let useg1 = uSegments + 1; + let a = ix + iy * useg1; + let b = ix + (iy + 1) * useg1; + let c = (ix + 1) + (iy + 1) * useg1; + let d = (ix + 1) + iy * useg1; + + if (inv) { + indices.push(offset + a, offset + b, offset + d); + indices.push(offset + d, offset + b, offset + c); + } else { + indices.push(offset + a, offset + d, offset + b); + indices.push(offset + b, offset + d, offset + c); + } + } + } + } + } + + _buildPlane(0, ws, hs); // FRONT + _buildPlane(4, ls, hs); // RIGHT + _buildPlane(1, ws, hs); // BACK + _buildPlane(5, ls, hs); // LEFT + _buildPlane(3, ws, ls); // BOTTOM + _buildPlane(2, ws, ls); // TOP + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); +} diff --git a/cocos2d/core/3d/primitive/capsule.ts b/cocos2d/core/3d/primitive/capsule.ts new file mode 100644 index 00000000000..588939d7a07 --- /dev/null +++ b/cocos2d/core/3d/primitive/capsule.ts @@ -0,0 +1,203 @@ +'use strict'; + +import Vec3 from '../../value-types/vec3'; +import VertexData from './vertex-data'; + +let temp1 = cc.v3(0, 0, 0); +let temp2 = cc.v3(0, 0, 0); + +/** + * @param {Number} radiusTop + * @param {Number} radiusBottom + * @param {Number} height + * @param {Object} opts + * @param {Number} opts.sides + * @param {Number} opts.heightSegments + * @param {Boolean} opts.capped + * @param {Number} opts.arc + */ +export default function (radiusTop = 0.5, radiusBottom = 0.5, height = 2, opts = {sides: 32, heightSegments: 32, arc: 2.0 * Math.PI}) { + let torsoHeight = height - radiusTop - radiusBottom; + let sides = opts.sides; + let heightSegments = opts.heightSegments; + let bottomProp = radiusBottom / height; + let torProp = torsoHeight / height; + let topProp = radiusTop / height; + let bottomSegments = Math.floor(heightSegments * bottomProp); + let topSegments = Math.floor(heightSegments * topProp); + let torSegments = Math.floor(heightSegments * torProp); + let topOffset = torsoHeight + radiusBottom - height / 2; + let torOffset = radiusBottom - height / 2; + let bottomOffset = radiusBottom - height / 2; + let arc = opts.arc; + + // calculate vertex count + let positions: number[] = []; + let normals: number[] = []; + let uvs: number[] = []; + let indices: number[] = []; + let maxRadius = Math.max(radiusTop, radiusBottom); + let minPos = cc.v3(-maxRadius, -height / 2, -maxRadius); + let maxPos = cc.v3(maxRadius, height / 2, maxRadius); + let boundingRadius = height / 2; + + let index = 0; + let indexArray: number[][] = []; + + generateBottom(); + + generateTorso(); + + generateTop(); + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); + + // ======================= + // internal fucntions + // ======================= + + function generateTorso() { + // this will be used to calculate the normal + let slope = (radiusTop - radiusBottom) / torsoHeight; + + // generate positions, normals and uvs + for (let y = 0; y <= torSegments; y++) { + + let indexRow: number[] = []; + let lat = y / torSegments; + let radius = lat * (radiusTop - radiusBottom) + radiusBottom; + + for (let x = 0; x <= sides; ++x) { + let u = x / sides; + let v = lat * torProp + bottomProp; + let theta = u * arc - (arc / 4); + + let sinTheta = Math.sin(theta); + let cosTheta = Math.cos(theta); + + // vertex + positions.push(radius * sinTheta); + positions.push(lat * torsoHeight + torOffset); + positions.push(radius * cosTheta); + + // normal + Vec3.normalize(temp1, Vec3.set(temp2, sinTheta, -slope, cosTheta)); + normals.push(temp1.x); + normals.push(temp1.y); + normals.push(temp1.z); + + // uv + uvs.push(u,v); + // save index of vertex in respective row + indexRow.push(index); + + // increase index + ++index; + } + + // now save positions of the row in our index array + indexArray.push(indexRow); + } + + // generate indices + for (let y = 0; y < torSegments; ++y) { + for (let x = 0; x < sides; ++x) { + // we use the index array to access the correct indices + let i1 = indexArray[y][x]; + let i2 = indexArray[y + 1][x]; + let i3 = indexArray[y + 1][x + 1]; + let i4 = indexArray[y][x + 1]; + + // face one + indices.push(i1); + indices.push(i4); + indices.push(i2); + + // face two + indices.push(i4); + indices.push(i3); + indices.push(i2); + } + } + } + + function generateBottom() { + for (let lat = 0; lat <= bottomSegments; ++lat) { + let theta = lat * Math.PI / bottomSegments / 2; + let sinTheta = Math.sin(theta); + let cosTheta = -Math.cos(theta); + + for (let lon = 0; lon <= sides; ++lon) { + let phi = lon * 2 * Math.PI / sides - Math.PI / 2.0; + let sinPhi = Math.sin(phi); + let cosPhi = Math.cos(phi); + + let x = sinPhi * sinTheta; + let y = cosTheta; + let z = cosPhi * sinTheta; + let u = lon / sides; + let v = lat / heightSegments; + + positions.push(x * radiusBottom, y * radiusBottom + bottomOffset, z * radiusBottom); + normals.push(x, y, z); + uvs.push(u, v); + + if ((lat < bottomSegments) && (lon < sides)) { + let seg1 = sides + 1; + let a = seg1 * lat + lon; + let b = seg1 * (lat + 1) + lon; + let c = seg1 * (lat + 1) + lon + 1; + let d = seg1 * lat + lon + 1; + + indices.push(a, d, b); + indices.push(d, c, b); + } + + ++index; + } + } + } + + function generateTop() { + for (let lat = 0; lat <= topSegments; ++lat) { + let theta = lat * Math.PI / topSegments / 2 + Math.PI / 2; + let sinTheta = Math.sin(theta); + let cosTheta = -Math.cos(theta); + + for (let lon = 0; lon <= sides; ++lon) { + let phi = lon * 2 * Math.PI / sides - Math.PI / 2.0; + let sinPhi = Math.sin(phi); + let cosPhi = Math.cos(phi); + + let x = sinPhi * sinTheta; + let y = cosTheta; + let z = cosPhi * sinTheta; + let u = lon / sides; + let v = lat / heightSegments + (1-topProp); + + positions.push(x * radiusTop, y * radiusTop + topOffset, z * radiusTop); + normals.push(x, y, z); + uvs.push(u, v); + + if ((lat < topSegments) && (lon < sides)) { + let seg1 = sides + 1; + let a = seg1 * lat + lon + indexArray[torSegments][sides] + 1; + let b = seg1 * (lat + 1) + lon + indexArray[torSegments][sides] + 1; + let c = seg1 * (lat + 1) + lon + 1 + indexArray[torSegments][sides] + 1; + let d = seg1 * lat + lon + 1 + indexArray[torSegments][sides] + 1; + + indices.push(a, d, b); + indices.push(d, c, b); + } + } + } + } +} diff --git a/cocos2d/core/3d/primitive/cone.ts b/cocos2d/core/3d/primitive/cone.ts new file mode 100644 index 00000000000..1981d3510c7 --- /dev/null +++ b/cocos2d/core/3d/primitive/cone.ts @@ -0,0 +1,16 @@ +'use strict'; + +import cylinder from './cylinder'; + +/** + * @param {Number} radius + * @param {Number} height + * @param {Object} opts + * @param {Number} opts.radialSegments + * @param {Number} opts.heightSegments + * @param {Boolean} opts.capped + * @param {Number} opts.arc + */ +export default function (radius = 0.5, height = 1, opts = {radialSegments: 32, heightSegments: 1, capped: true, arc: 2.0 * Math.PI}) { + return cylinder(0, radius, height, opts); +} diff --git a/cocos2d/core/3d/primitive/cylinder.ts b/cocos2d/core/3d/primitive/cylinder.ts new file mode 100644 index 00000000000..aa4deb46d1c --- /dev/null +++ b/cocos2d/core/3d/primitive/cylinder.ts @@ -0,0 +1,238 @@ +'use strict'; + +import Vec3 from '../../value-types/vec3'; +import VertexData from './vertex-data'; + +let temp1 = new Vec3(); +let temp2 = new Vec3(); + +/** + * @param {Number} radiusTop + * @param {Number} radiusBottom + * @param {Number} height + * @param {Object} opts + * @param {Number} opts.radialSegments + * @param {Number} opts.heightSegments + * @param {Boolean} opts.capped + * @param {Number} opts.arc + */ +export default function (radiusTop = 0.5, radiusBottom = 0.5, height = 2, opts = {radialSegments: 32, heightSegments: 1, capped: true, arc: 2.0 * Math.PI}) { + let halfHeight = height * 0.5; + let radialSegments = opts.radialSegments; + let heightSegments = opts.heightSegments; + let capped = opts.capped; + let arc = opts.arc; + + let cntCap = 0; + if (!capped) { + if (radiusTop > 0) { + cntCap++; + } + + if (radiusBottom > 0) { + cntCap++; + } + } + + // calculate vertex count + let vertCount = (radialSegments + 1) * (heightSegments + 1); + if (capped) { + vertCount += ((radialSegments + 1) * cntCap) + (radialSegments * cntCap); + } + + // calculate index count + let indexCount = radialSegments * heightSegments * 2 * 3; + if (capped) { + indexCount += radialSegments * cntCap * 3; + } + + let indices = new Array(indexCount); + let positions = new Array(vertCount * 3); + let normals = new Array(vertCount * 3); + let uvs = new Array(vertCount * 2); + let maxRadius = Math.max(radiusTop, radiusBottom); + let minPos = new Vec3(-maxRadius, -halfHeight, -maxRadius); + let maxPos = new Vec3(maxRadius, halfHeight, maxRadius); + let boundingRadius = Math.sqrt(maxRadius * maxRadius + halfHeight * halfHeight); + + let index = 0; + let indexOffset = 0; + + generateTorso(); + + if (capped) { + if (radiusBottom > 0) { + generateCap(false); + } + + if (radiusTop > 0) { + generateCap(true); + } + } + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); + + // ======================= + // internal fucntions + // ======================= + + function generateTorso() { + let indexArray: number[][] = []; + + // this will be used to calculate the normal + let r = radiusTop - radiusBottom; + let slope = r * r / height * Math.sign(r); + + // generate positions, normals and uvs + for (let y = 0; y <= heightSegments; y++) { + let indexRow: number[] = []; + let v = y / heightSegments; + + // calculate the radius of the current row + let radius = v * r + radiusBottom; + + for (let x = 0; x <= radialSegments; ++x) { + let u = x / radialSegments; + let theta = u * arc; + + let sinTheta = Math.sin(theta); + let cosTheta = Math.cos(theta); + + // vertex + positions[3 * index] = radius * sinTheta; + positions[3 * index + 1] = v * height - halfHeight; + positions[3 * index + 2] = radius * cosTheta; + + // normal + Vec3.normalize(temp1, Vec3.set(temp2, sinTheta, -slope, cosTheta)); + normals[3 * index] = temp1.x; + normals[3 * index + 1] = temp1.y; + normals[3 * index + 2] = temp1.z; + + // uv + uvs[2 * index] = (1 - u) * 2 % 1; + uvs[2 * index + 1] = v; + + // save index of vertex in respective row + indexRow.push(index); + + // increase index + ++index; + } + + // now save positions of the row in our index array + indexArray.push(indexRow); + } + + // generate indices + for (let y = 0; y < heightSegments; ++y) { + for (let x = 0; x < radialSegments; ++x) { + // we use the index array to access the correct indices + let i1 = indexArray[y][x]; + let i2 = indexArray[y + 1][x]; + let i3 = indexArray[y + 1][x + 1]; + let i4 = indexArray[y][x + 1]; + + // face one + indices[indexOffset] = i1; ++indexOffset; + indices[indexOffset] = i4; ++indexOffset; + indices[indexOffset] = i2; ++indexOffset; + + // face two + indices[indexOffset] = i4; ++indexOffset; + indices[indexOffset] = i3; ++indexOffset; + indices[indexOffset] = i2; ++indexOffset; + } + } + } + + function generateCap(top) { + let centerIndexStart, centerIndexEnd; + + let radius = top ? radiusTop : radiusBottom; + let sign = top ? 1 : - 1; + + // save the index of the first center vertex + centerIndexStart = index; + + // first we generate the center vertex data of the cap. + // because the geometry needs one set of uvs per face, + // we must generate a center vertex per face/segment + + for (let x = 1; x <= radialSegments; ++x) { + // vertex + positions[3 * index] = 0; + positions[3 * index + 1] = halfHeight * sign; + positions[3 * index + 2] = 0; + + // normal + normals[3 * index] = 0; + normals[3 * index + 1] = sign; + normals[3 * index + 2] = 0; + + // uv + uvs[2 * index] = 0.5; + uvs[2 * index + 1] = 0.5; + + // increase index + ++index; + } + + // save the index of the last center vertex + centerIndexEnd = index; + + // now we generate the surrounding positions, normals and uvs + + for (let x = 0; x <= radialSegments; ++x) { + let u = x / radialSegments; + let theta = u * arc; + + let cosTheta = Math.cos(theta); + let sinTheta = Math.sin(theta); + + // vertex + positions[3 * index] = radius * sinTheta; + positions[3 * index + 1] = halfHeight * sign; + positions[3 * index + 2] = radius * cosTheta; + + // normal + normals[3 * index] = 0; + normals[3 * index + 1] = sign; + normals[3 * index + 2] = 0; + + // uv + uvs[2 * index] = 0.5 - (sinTheta * 0.5 * sign); + uvs[2 * index + 1] = 0.5 + (cosTheta * 0.5); + + // increase index + ++index; + } + + // generate indices + + for (let x = 0; x < radialSegments; ++x) { + let c = centerIndexStart + x; + let i = centerIndexEnd + x; + + if (top) { + // face top + indices[indexOffset] = i + 1; ++indexOffset; + indices[indexOffset] = c; ++indexOffset; + indices[indexOffset] = i; ++indexOffset; + } else { + // face bottom + indices[indexOffset] = c; ++indexOffset; + indices[indexOffset] = i + 1; ++indexOffset; + indices[indexOffset] = i; ++indexOffset; + } + } + } +} diff --git a/cocos2d/core/3d/primitive/index.ts b/cocos2d/core/3d/primitive/index.ts new file mode 100644 index 00000000000..d20fc35243c --- /dev/null +++ b/cocos2d/core/3d/primitive/index.ts @@ -0,0 +1,152 @@ +import * as utils from './utils'; +import box from './box'; +import cone from './cone'; +import cylinder from './cylinder'; +import plane from './plane'; +import quad from './quad'; +import sphere from './sphere'; +import torus from './torus'; +import capsule from './capsule'; +import { PolyhedronType, polyhedron } from './polyhedron'; +import VertexData from './vertex-data'; + +/** + * !#en A basic module for creating vertex data for 3D objects. You can access this module by `cc.primitive`. + * !#zh 一个创建 3D 物体顶点数据的基础模块,你可以通过 `cc.primitive` 来访问这个模块。 + * @module cc.primitive + * @submodule cc.primitive + * @main + */ + +cc.primitive = Object.assign({ + /** + * !#en Create box vertex data + * !#zh 创建长方体顶点数据 + * @method box + * @static + * @param {Number} width + * @param {Number} height + * @param {Number} length + * @param {Object} opts + * @param {Number} opts.widthSegments + * @param {Number} opts.heightSegments + * @param {Number} opts.lengthSegments + * @return {primitive.VertexData} + */ + box, + /** + * !#en Create cone vertex data + * !#zh 创建圆锥体顶点数据 + * @method cone + * @static + * @param {Number} radius + * @param {Number} height + * @param {Object} opts + * @param {Number} opts.radialSegments + * @param {Number} opts.heightSegments + * @param {Boolean} opts.capped + * @param {Number} opts.arc + * @return {primitive.VertexData} + */ + cone, + /** + * !#en Create cylinder vertex data + * !#zh 创建圆柱体顶点数据 + * @method cylinder + * @static + * @param {Number} radiusTop + * @param {Number} radiusBottom + * @param {Number} height + * @param {Object} opts + * @param {Number} opts.radialSegments + * @param {Number} opts.heightSegments + * @param {Boolean} opts.capped + * @param {Number} opts.arc + * @return {primitive.VertexData} + */ + cylinder, + /** + * !#en Create plane vertex data + * !#zh 创建平台顶点数据 + * @method plane + * @static + * @param {Number} width + * @param {Number} length + * @param {Object} opts + * @param {Number} opts.widthSegments + * @param {Number} opts.lengthSegments + * @return {primitive.VertexData} + */ + plane, + /** + * !#en Create quad vertex data + * !#zh 创建面片顶点数据 + * @method quad + * @static + * @return {primitive.VertexData} + */ + quad, + /** + * !#en Create sphere vertex data + * !#zh 创建球体顶点数据 + * @method sphere + * @static + * @param {Number} radius + * @param {Object} opts + * @param {Number} opts.segments + * @return {primitive.VertexData} + */ + sphere, + /** + * !#en Create torus vertex data + * !#zh 创建圆环顶点数据 + * @method torus + * @static + * @param {Number} radius + * @param {Number} tube + * @param {Object} opts + * @param {Number} opts.radialSegments + * @param {Number} opts.tubularSegments + * @param {Number} opts.arc + * @return {primitive.VertexData} + */ + torus, + /** + * !#en Create capsule vertex data + * !#zh 创建胶囊体顶点数据 + * @method capsule + * @static + * @param {Number} radiusTop + * @param {Number} radiusBottom + * @param {Number} height + * @param {Object} opts + * @param {Number} opts.sides + * @param {Number} opts.heightSegments + * @param {Boolean} opts.capped + * @param {Number} opts.arc + * @return {primitive.VertexData} + */ + capsule, + /** + * !#en Create polyhedron vertex data + * !#zh 创建多面体顶点数据 + * @method polyhedron + * @static + * @param {primitive.PolyhedronType} type + * @param {Number} Size + * @param {Object} opts + * @param {Number} opts.sizeX + * @param {Number} opts.sizeY + * @param {Number} opts.sizeZ + * @return {primitive.VertexData} + */ + polyhedron, + + PolyhedronType, + VertexData, +}, utils); + +// fix submodule pollute ... +/** + * @submodule cc + */ diff --git a/cocos2d/core/3d/primitive/plane.ts b/cocos2d/core/3d/primitive/plane.ts new file mode 100644 index 00000000000..8f2b347bb1c --- /dev/null +++ b/cocos2d/core/3d/primitive/plane.ts @@ -0,0 +1,76 @@ +'use strict'; + +import Vec3 from '../../value-types/vec3'; +import VertexData from './vertex-data'; + +let temp1 = new Vec3(); +let temp2 = new Vec3(); +let temp3 = new Vec3(); +let r = new Vec3(); +let c00 = new Vec3(); +let c10 = new Vec3(); +let c01 = new Vec3(); + +/** + * @param {Number} width + * @param {Number} length + * @param {Object} opts + * @param {Number} opts.widthSegments + * @param {Number} opts.lengthSegments + */ +export default function (width = 10, length = 10, opts = {widthSegments: 10, lengthSegments: 10}) { + let uSegments = opts.widthSegments; + let vSegments = opts.lengthSegments; + + let hw = width * 0.5; + let hl = length * 0.5; + + let positions: number[] = []; + let normals: number[] = []; + let uvs: number[] = []; + let indices: number[] = []; + let minPos = new Vec3(-hw, 0, -hl); + let maxPos = new Vec3(hw, 0, hl); + let boundingRadius = Math.sqrt(width * width + length * length); + + Vec3.set(c00, -hw, 0, hl); + Vec3.set(c10, hw, 0, hl); + Vec3.set(c01, -hw, 0, -hl); + + for (let y = 0; y <= vSegments; y++) { + for (let x = 0; x <= uSegments; x++) { + let u = x / uSegments; + let v = y / vSegments; + + Vec3.lerp(temp1, c00, c10, u); + Vec3.lerp(temp2, c00, c01, v); + Vec3.sub(temp3, temp2, c00); + Vec3.add(r, temp1, temp3); + + positions.push(r.x, r.y, r.z); + normals.push(0, 1, 0); + uvs.push(u, v); + + if ((x < uSegments) && (y < vSegments)) { + let useg1 = uSegments + 1; + let a = x + y * useg1; + let b = x + (y + 1) * useg1; + let c = (x + 1) + (y + 1) * useg1; + let d = (x + 1) + y * useg1; + + indices.push(a, d, b); + indices.push(d, c, b); + } + } + } + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); +} diff --git a/cocos2d/core/3d/primitive/polyhedron.ts b/cocos2d/core/3d/primitive/polyhedron.ts new file mode 100644 index 00000000000..e6f9ac90f2f --- /dev/null +++ b/cocos2d/core/3d/primitive/polyhedron.ts @@ -0,0 +1,177 @@ + +import { calcNormals } from './utils'; +import VertexData from './vertex-data'; +import { Vec3 } from '../../value-types'; + +interface PolyhedronData { + vertex: number[][], + face: number[][] +} + +/** + * @enum primitive.PolyhedronType + * @static + * @namespace primitive + */ +export const PolyhedronType = cc.Enum({ + /** + * @property Tetrahedron + * @static + */ + Tetrahedron: 0, + /** + * @property Octahedron + * @static + */ + Octahedron: 1, + /** + * @property Dodecahedron + * @static + */ + Dodecahedron: 2, + /** + * @property Icosahedron + * @static + */ + Icosahedron: 3, + /** + * @property Rhombicuboctahedron + * @static + */ + Rhombicuboctahedron: 4, + /** + * @property TriangularPrism + * @static + */ + TriangularPrism: 5, + /** + * @property PentagonalPrism + * @static + */ + PentagonalPrism: 6, + /** + * @property HexagonalPrism + * @static + */ + HexagonalPrism: 7, + /** + * @property SquarePyramid + * @static + */ + SquarePyramid: 8, + /** + * @property PentagonalPyramid + * @static + */ + PentagonalPyramid: 9, + /** + * @property TriangularDipyramid + * @static + */ + TriangularDipyramid: 10, + /** + * @property PentagonalDipyramid + * @static + */ + PentagonalDipyramid: 11, + /** + * @property ElongatedSquareDipyramid + * @static + */ + ElongatedSquareDipyramid: 12, + /** + * @property ElongatedPentagonalDipyramid + * @static + */ + ElongatedPentagonalDipyramid: 13, + /** + * @property ElongatedPentagonalCupola + * @static + */ + ElongatedPentagonalCupola: 14 +}); + +// precached polyhedra data +let polyhedra: PolyhedronData[] = []; +polyhedra[0] = { vertex: [[0, 0, 1.732051], [1.632993, 0, -0.5773503], [-0.8164966, 1.414214, -0.5773503], [-0.8164966, -1.414214, -0.5773503]], face: [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]] }; +polyhedra[1] = { vertex: [[0, 0, 1.414214], [1.414214, 0, 0], [0, 1.414214, 0], [-1.414214, 0, 0], [0, -1.414214, 0], [0, 0, -1.414214]], face: [[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [3, 5, 4]] }; +polyhedra[2] = { + vertex: [[0, 0, 1.070466], [0.7136442, 0, 0.7978784], [-0.3568221, 0.618034, 0.7978784], [-0.3568221, -0.618034, 0.7978784], [0.7978784, 0.618034, 0.3568221], [0.7978784, -0.618034, 0.3568221], [-0.9341724, 0.381966, 0.3568221], [0.1362939, 1, 0.3568221], [0.1362939, -1, 0.3568221], [-0.9341724, -0.381966, 0.3568221], [0.9341724, 0.381966, -0.3568221], [0.9341724, -0.381966, -0.3568221], [-0.7978784, 0.618034, -0.3568221], [-0.1362939, 1, -0.3568221], [-0.1362939, -1, -0.3568221], [-0.7978784, -0.618034, -0.3568221], [0.3568221, 0.618034, -0.7978784], [0.3568221, -0.618034, -0.7978784], [-0.7136442, 0, -0.7978784], [0, 0, -1.070466]], + face: [[0, 1, 4, 7, 2], [0, 2, 6, 9, 3], [0, 3, 8, 5, 1], [1, 5, 11, 10, 4], [2, 7, 13, 12, 6], [3, 9, 15, 14, 8], [4, 10, 16, 13, 7], [5, 8, 14, 17, 11], [6, 12, 18, 15, 9], [10, 11, 17, 19, 16], [12, 13, 16, 19, 18], [14, 15, 18, 19, 17]] +}; +polyhedra[3] = { + vertex: [[0, 0, 1.175571], [1.051462, 0, 0.5257311], [0.3249197, 1, 0.5257311], [-0.8506508, 0.618034, 0.5257311], [-0.8506508, -0.618034, 0.5257311], [0.3249197, -1, 0.5257311], [0.8506508, 0.618034, -0.5257311], [0.8506508, -0.618034, -0.5257311], [-0.3249197, 1, -0.5257311], [-1.051462, 0, -0.5257311], [-0.3249197, -1, -0.5257311], [0, 0, -1.175571]], + face: [[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 5, 7], [1, 7, 6], [1, 6, 2], [2, 6, 8], [2, 8, 3], [3, 8, 9], [3, 9, 4], [4, 9, 10], [4, 10, 5], [5, 10, 7], [6, 7, 11], [6, 11, 8], [7, 10, 11], [8, 11, 9], [9, 11, 10]] +}; +polyhedra[4] = { + vertex: [[0, 0, 1.070722], [0.7148135, 0, 0.7971752], [-0.104682, 0.7071068, 0.7971752], [-0.6841528, 0.2071068, 0.7971752], [-0.104682, -0.7071068, 0.7971752], [0.6101315, 0.7071068, 0.5236279], [1.04156, 0.2071068, 0.1367736], [0.6101315, -0.7071068, 0.5236279], [-0.3574067, 1, 0.1367736], [-0.7888348, -0.5, 0.5236279], [-0.9368776, 0.5, 0.1367736], [-0.3574067, -1, 0.1367736], [0.3574067, 1, -0.1367736], [0.9368776, -0.5, -0.1367736], [0.7888348, 0.5, -0.5236279], [0.3574067, -1, -0.1367736], [-0.6101315, 0.7071068, -0.5236279], [-1.04156, -0.2071068, -0.1367736], [-0.6101315, -0.7071068, -0.5236279], [0.104682, 0.7071068, -0.7971752], [0.6841528, -0.2071068, -0.7971752], [0.104682, -0.7071068, -0.7971752], [-0.7148135, 0, -0.7971752], [0, 0, -1.070722]], + face: [[0, 2, 3], [1, 6, 5], [4, 9, 11], [7, 15, 13], [8, 16, 10], [12, 14, 19], [17, 22, 18], [20, 21, 23], [0, 1, 5, 2], [0, 3, 9, 4], [0, 4, 7, 1], [1, 7, 13, 6], [2, 5, 12, 8], [2, 8, 10, 3], [3, 10, 17, 9], [4, 11, 15, 7], [5, 6, 14, 12], [6, 13, 20, 14], [8, 12, 19, 16], [9, 17, 18, 11], [10, 16, 22, 17], [11, 18, 21, 15], [13, 15, 21, 20], [14, 20, 23, 19], [16, 19, 23, 22], [18, 22, 23, 21]] +}; +polyhedra[5] = { vertex: [[0, 0, 1.322876], [1.309307, 0, 0.1889822], [-0.9819805, 0.8660254, 0.1889822], [0.1636634, -1.299038, 0.1889822], [0.3273268, 0.8660254, -0.9449112], [-0.8183171, -0.4330127, -0.9449112]], face: [[0, 3, 1], [2, 4, 5], [0, 1, 4, 2], [0, 2, 5, 3], [1, 3, 5, 4]] }; +polyhedra[6] = { vertex: [[0, 0, 1.159953], [1.013464, 0, 0.5642542], [-0.3501431, 0.9510565, 0.5642542], [-0.7715208, -0.6571639, 0.5642542], [0.6633206, 0.9510565, -0.03144481], [0.8682979, -0.6571639, -0.3996071], [-1.121664, 0.2938926, -0.03144481], [-0.2348831, -1.063314, -0.3996071], [0.5181548, 0.2938926, -0.9953061], [-0.5850262, -0.112257, -0.9953061]], face: [[0, 1, 4, 2], [0, 2, 6, 3], [1, 5, 8, 4], [3, 6, 9, 7], [5, 7, 9, 8], [0, 3, 7, 5, 1], [2, 4, 8, 9, 6]] }; +polyhedra[7] = { vertex: [[0, 0, 1.118034], [0.8944272, 0, 0.6708204], [-0.2236068, 0.8660254, 0.6708204], [-0.7826238, -0.4330127, 0.6708204], [0.6708204, 0.8660254, 0.2236068], [1.006231, -0.4330127, -0.2236068], [-1.006231, 0.4330127, 0.2236068], [-0.6708204, -0.8660254, -0.2236068], [0.7826238, 0.4330127, -0.6708204], [0.2236068, -0.8660254, -0.6708204], [-0.8944272, 0, -0.6708204], [0, 0, -1.118034]], face: [[0, 1, 4, 2], [0, 2, 6, 3], [1, 5, 8, 4], [3, 6, 10, 7], [5, 9, 11, 8], [7, 10, 11, 9], [0, 3, 7, 9, 5, 1], [2, 4, 8, 11, 10, 6]] }; +polyhedra[8] = { vertex: [[-0.729665, 0.670121, 0.319155], [-0.655235, -0.29213, -0.754096], [-0.093922, -0.607123, 0.537818], [0.702196, 0.595691, 0.485187], [0.776626, -0.36656, -0.588064]], face: [[1, 4, 2], [0, 1, 2], [3, 0, 2], [4, 3, 2], [4, 1, 0, 3]] }; +polyhedra[9] = { vertex: [[-0.868849, -0.100041, 0.61257], [-0.329458, 0.976099, 0.28078], [-0.26629, -0.013796, -0.477654], [-0.13392, -1.034115, 0.229829], [0.738834, 0.707117, -0.307018], [0.859683, -0.535264, -0.338508]], face: [[3, 0, 2], [5, 3, 2], [4, 5, 2], [1, 4, 2], [0, 1, 2], [0, 3, 5, 4, 1]] }; +polyhedra[10] = { vertex: [[-0.610389, 0.243975, 0.531213], [-0.187812, -0.48795, -0.664016], [-0.187812, 0.9759, -0.664016], [0.187812, -0.9759, 0.664016], [0.798201, 0.243975, 0.132803]], face: [[1, 3, 0], [3, 4, 0], [3, 1, 4], [0, 2, 1], [0, 4, 2], [2, 4, 1]] }; +polyhedra[11] = { vertex: [[-1.028778, 0.392027, -0.048786], [-0.640503, -0.646161, 0.621837], [-0.125162, -0.395663, -0.540059], [0.004683, 0.888447, -0.651988], [0.125161, 0.395663, 0.540059], [0.632925, -0.791376, 0.433102], [1.031672, 0.157063, -0.354165]], face: [[3, 2, 0], [2, 1, 0], [2, 5, 1], [0, 4, 3], [0, 1, 4], [4, 1, 5], [2, 3, 6], [3, 4, 6], [5, 2, 6], [4, 5, 6]] }; +polyhedra[12] = { vertex: [[-0.669867, 0.334933, -0.529576], [-0.669867, 0.334933, 0.529577], [-0.4043, 1.212901, 0], [-0.334933, -0.669867, -0.529576], [-0.334933, -0.669867, 0.529577], [0.334933, 0.669867, -0.529576], [0.334933, 0.669867, 0.529577], [0.4043, -1.212901, 0], [0.669867, -0.334933, -0.529576], [0.669867, -0.334933, 0.529577]], face: [[8, 9, 7], [6, 5, 2], [3, 8, 7], [5, 0, 2], [4, 3, 7], [0, 1, 2], [9, 4, 7], [1, 6, 2], [9, 8, 5, 6], [8, 3, 0, 5], [3, 4, 1, 0], [4, 9, 6, 1]] }; +polyhedra[13] = { vertex: [[-0.931836, 0.219976, -0.264632], [-0.636706, 0.318353, 0.692816], [-0.613483, -0.735083, -0.264632], [-0.326545, 0.979634, 0], [-0.318353, -0.636706, 0.692816], [-0.159176, 0.477529, -0.856368], [0.159176, -0.477529, -0.856368], [0.318353, 0.636706, 0.692816], [0.326545, -0.979634, 0], [0.613482, 0.735082, -0.264632], [0.636706, -0.318353, 0.692816], [0.931835, -0.219977, -0.264632]], face: [[11, 10, 8], [7, 9, 3], [6, 11, 8], [9, 5, 3], [2, 6, 8], [5, 0, 3], [4, 2, 8], [0, 1, 3], [10, 4, 8], [1, 7, 3], [10, 11, 9, 7], [11, 6, 5, 9], [6, 2, 0, 5], [2, 4, 1, 0], [4, 10, 7, 1]] }; +polyhedra[14] = { + vertex: [[-0.93465, 0.300459, -0.271185], [-0.838689, -0.260219, -0.516017], [-0.711319, 0.717591, 0.128359], [-0.710334, -0.156922, 0.080946], [-0.599799, 0.556003, -0.725148], [-0.503838, -0.004675, -0.969981], [-0.487004, 0.26021, 0.48049], [-0.460089, -0.750282, -0.512622], [-0.376468, 0.973135, -0.325605], [-0.331735, -0.646985, 0.084342], [-0.254001, 0.831847, 0.530001], [-0.125239, -0.494738, -0.966586], [0.029622, 0.027949, 0.730817], [0.056536, -0.982543, -0.262295], [0.08085, 1.087391, 0.076037], [0.125583, -0.532729, 0.485984], [0.262625, 0.599586, 0.780328], [0.391387, -0.726999, -0.716259], [0.513854, -0.868287, 0.139347], [0.597475, 0.85513, 0.326364], [0.641224, 0.109523, 0.783723], [0.737185, -0.451155, 0.538891], [0.848705, -0.612742, -0.314616], [0.976075, 0.365067, 0.32976], [1.072036, -0.19561, 0.084927]], + face: [[15, 18, 21], [12, 20, 16], [6, 10, 2], [3, 0, 1], [9, 7, 13], [2, 8, 4, 0], [0, 4, 5, 1], [1, 5, 11, 7], [7, 11, 17, 13], [13, 17, 22, 18], [18, 22, 24, 21], [21, 24, 23, 20], [20, 23, 19, 16], [16, 19, 14, 10], [10, 14, 8, 2], [15, 9, 13, 18], [12, 15, 21, 20], [6, 12, 16, 10], [3, 6, 2, 0], [9, 3, 1, 7], [9, 15, 12, 6, 3], [22, 17, 11, 5, 4, 8, 14, 19, 23, 24]] +}; + +export const polyhedron = function (type, size = 1, opts = {sizeX: 0, sizeY: 0, sizeZ: 0}) { + type = type && (type < 0 || type >= polyhedra.length) ? 0 : type || 0; + let sizeX = opts.sizeX || size; + let sizeY = opts.sizeY || size; + let sizeZ = opts.sizeZ || size; + let data = polyhedra[type]; + let nbfaces = data.face.length; + + let positions: number[] = []; + let indices: number[] = []; + let normals: number[] = []; + let uvs: number[] = []; + let minPos = new Vec3(Infinity, Infinity, Infinity); + let maxPos = new Vec3(-Infinity, -Infinity, -Infinity); + + for (let i = 0; i < data.vertex.length; i++) { + let x = data.vertex[i][0] * sizeX; + let y = data.vertex[i][1] * sizeY; + let z = data.vertex[i][2] * sizeZ; + + minPos.x = Math.min(minPos.x, x); + minPos.y = Math.min(minPos.y, y); + minPos.z = Math.min(minPos.z, z); + maxPos.x = Math.max(maxPos.x, x); + maxPos.y = Math.max(maxPos.y, y); + maxPos.z = Math.max(maxPos.z, z); + + positions.push(x, y, z); + uvs.push(0, 0); + } + for (let f = 0; f < nbfaces; f++) { + for (let i = 0; i < data.face[f].length - 2; i++) { + indices.push(data.face[f][0], data.face[f][i + 2], data.face[f][i + 1]); + } + } + + calcNormals(positions, indices, normals); + + let boundingRadius = Math.sqrt( + Math.pow(maxPos.x - minPos.x, 2), + Math.pow(maxPos.y - minPos.y, 2), + Math.pow(maxPos.z - minPos.z, 2) + ); + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); +}; diff --git a/cocos2d/core/3d/primitive/quad.ts b/cocos2d/core/3d/primitive/quad.ts new file mode 100644 index 00000000000..e92f2dce460 --- /dev/null +++ b/cocos2d/core/3d/primitive/quad.ts @@ -0,0 +1,47 @@ +'use strict'; + +import VertexData from './vertex-data'; +import { Vec3 } from '../../value-types'; + +let positions = [ + -0.5, -0.5, 0, // bottom-left + -0.5, 0.5, 0, // top-left + 0.5, 0.5, 0, // top-right + 0.5, -0.5, 0, // bottom-right +]; + +let normals = [ + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, +]; + +let uvs = [ + 0, 0, + 0, 1, + 1, 1, + 1, 0, +]; + +let indices = [ + 0, 3, 1, + 3, 2, 1 +]; + +// TODO: ? +let minPos = new Vec3(-0.5, -0.5, 0); +let maxPos = new Vec3(0.5, 0.5, 0); +let boundingRadius = Math.sqrt(0.5 * 0.5 + 0.5 * 0.5); + +export default function () { + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); +} diff --git a/cocos2d/core/3d/primitive/sphere.ts b/cocos2d/core/3d/primitive/sphere.ts new file mode 100644 index 00000000000..7fd8a9912af --- /dev/null +++ b/cocos2d/core/3d/primitive/sphere.ts @@ -0,0 +1,68 @@ +'use strict'; + +import VertexData from './vertex-data'; +import { Vec3 } from '../../value-types'; + +/** + * @param {Number} radius + * @param {Object} opts + * @param {Number} opts.segments + */ +export default function (radius = 0.5, opts = {segments: 32}) { + let segments = opts.segments; + + // lat === latitude + // lon === longitude + + let positions: number[] = []; + let normals: number[] = []; + let uvs: number[] = []; + let indices: number[] = []; + let minPos = new Vec3(-radius, -radius, -radius); + let maxPos = new Vec3(radius, radius, radius); + let boundingRadius = radius; + + for (let lat = 0; lat <= segments; ++lat) { + let theta = lat * Math.PI / segments; + let sinTheta = Math.sin(theta); + let cosTheta = -Math.cos(theta); + + for (let lon = 0; lon <= segments; ++lon) { + let phi = lon * 2 * Math.PI / segments - Math.PI / 2.0; + let sinPhi = Math.sin(phi); + let cosPhi = Math.cos(phi); + + let x = sinPhi * sinTheta; + let y = cosTheta; + let z = cosPhi * sinTheta; + let u = lon / segments; + let v = lat / segments; + + positions.push(x * radius, y * radius, z * radius); + normals.push(x, y, z); + uvs.push(u, v); + + + if ((lat < segments) && (lon < segments)) { + let seg1 = segments + 1; + let a = seg1 * lat + lon; + let b = seg1 * (lat + 1) + lon; + let c = seg1 * (lat + 1) + lon + 1; + let d = seg1 * lat + lon + 1; + + indices.push(a, d, b); + indices.push(d, c, b); + } + } + } + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); +} diff --git a/cocos2d/core/3d/primitive/torus.ts b/cocos2d/core/3d/primitive/torus.ts new file mode 100644 index 00000000000..1b1c4283431 --- /dev/null +++ b/cocos2d/core/3d/primitive/torus.ts @@ -0,0 +1,71 @@ +'use strict'; + +import VertexData from './vertex-data'; +import { Vec3 } from '../../value-types'; + +/** + * @param {Number} radius + * @param {Number} tube + * @param {Object} opts + * @param {Number} opts.radialSegments + * @param {Number} opts.tubularSegments + * @param {Number} opts.arc + */ +export default function (radius = 0.4, tube = 0.1, opts = {radialSegments: 32, tubularSegments: 32, arc: 2.0 * Math.PI}) { + let radialSegments = opts.radialSegments; + let tubularSegments = opts.tubularSegments; + let arc = opts.arc; + + let positions: number[] = []; + let normals: number[] = []; + let uvs: number[] = []; + let indices: number[] = []; + let minPos = new Vec3(-radius - tube, -tube, -radius - tube); + let maxPos = new Vec3(radius + tube, tube, radius + tube); + let boundingRadius = radius + tube; + + for (let j = 0; j <= radialSegments; j++) { + for (let i = 0; i <= tubularSegments; i++) { + let u = i / tubularSegments; + let v = j / radialSegments; + + let u1 = u * arc; + let v1 = v * Math.PI * 2; + + // vertex + let x = (radius + tube * Math.cos(v1)) * Math.sin(u1); + let y = tube * Math.sin(v1); + let z = (radius + tube * Math.cos(v1)) * Math.cos(u1); + + // this vector is used to calculate the normal + let nx = Math.sin(u1) * Math.cos(v1); + let ny = Math.sin(v1); + let nz = Math.cos(u1) * Math.cos(v1); + + positions.push(x, y, z); + normals.push(nx, ny, nz); + uvs.push(u, v); + + if ((i < tubularSegments) && (j < radialSegments)) { + let seg1 = tubularSegments + 1; + let a = seg1 * j + i; + let b = seg1 * (j + 1) + i; + let c = seg1 * (j + 1) + i + 1; + let d = seg1 * j + i + 1; + + indices.push(a, d, b); + indices.push(d, c, b); + } + } + } + + return new VertexData( + positions, + normals, + uvs, + indices, + minPos, + maxPos, + boundingRadius + ); +} diff --git a/cocos2d/core/3d/primitive/utils.ts b/cocos2d/core/3d/primitive/utils.ts new file mode 100644 index 00000000000..b0011469eb9 --- /dev/null +++ b/cocos2d/core/3d/primitive/utils.ts @@ -0,0 +1,126 @@ +import Vec3 from '../../value-types/vec3'; + +export function wireframe(indices) { + const offsets = [[0, 1], [1, 2], [2, 0]]; + let lines: number[] = []; + let lineIDs = {}; + + for (let i = 0; i < indices.length; i += 3) { + for (let k = 0; k < 3; ++k) { + let i1 = indices[i + offsets[k][0]]; + let i2 = indices[i + offsets[k][1]]; + + // check if we already have the line in our lines + let id = (i1 > i2) ? ((i2 << 16) | i1) : ((i1 << 16) | i2); + if (lineIDs[id] === undefined) { + lineIDs[id] = 0; + lines.push(i1, i2); + } + } + } + + return lines; +} + +export function invWinding(indices) { + let newIB : number[] = []; + for (let i = 0; i < indices.length; i += 3) + newIB.push(indices[i], indices[i + 2], indices[i + 1]); + return newIB; +} + +export function toWavefrontOBJ(primitive, scale = 1) { + let v = primitive.positions, t = primitive.uvs, n = primitive.normals, IB = primitive.indices; + let V = i => `${IB[i]+1}/${IB[i]+1}/${IB[i]+1}`; + let content = ''; + for (let i = 0; i < v.length; i += 3) + content += `v ${v[i]*scale} ${v[i+1]*scale} ${v[i+2]*scale}\n`; + for (let i = 0; i < t.length; i += 2) + content += `vt ${t[i]} ${t[i+1]}\n`; + for (let i = 0; i < n.length; i += 3) + content += `vn ${n[i]} ${n[i+1]} ${n[i+2]}\n`; + for (let i = 0; i < IB.length; i += 3) + content += `f ${V(i)} ${V(i+1)} ${V(i+2)}\n`; + return content; +} + +export function normals(positions, normals, length = 1) { + let verts = new Array(2 * positions.length); + + for (let i = 0; i < positions.length/3; ++i) { + let i3 = 3*i; + let i6 = 6*i; + + // line start + verts[i6 + 0] = positions[i3 + 0]; + verts[i6 + 1] = positions[i3 + 1]; + verts[i6 + 2] = positions[i3 + 2]; + + // line end + verts[i6 + 3] = positions[i3 + 0] + normals[i3 + 0] * length; + verts[i6 + 4] = positions[i3 + 1] + normals[i3 + 1] * length; + verts[i6 + 5] = positions[i3 + 2] + normals[i3 + 2] * length; + } + + return verts; +} + + +function fromArray (out, a, offset) { + out.x = a[offset]; + out.y = a[offset+1]; + out.z = a[offset+2]; +} + +export function calcNormals (positions, indices, normals) { + normals = normals || new Array(positions.length); + for (let i = 0, l = normals.length; i < l; i++) { + normals[i] = 0; + } + + let vA, vB, vC; + let pA = cc.v3(), pB = cc.v3(), pC = cc.v3(); + let cb = cc.v3(), ab = cc.v3(); + + for (let i = 0, il = indices.length; i < il; i += 3) { + + vA = indices[i + 0] * 3; + vB = indices[i + 1] * 3; + vC = indices[i + 2] * 3; + + fromArray(pA, positions, vA); + fromArray(pB, positions, vB); + fromArray(pC, positions, vC); + + Vec3.subtract(cb, pC, pB); + Vec3.subtract(ab, pA, pB); + Vec3.cross(cb, cb, ab); + + normals[vA] += cb.x; + normals[vA + 1] += cb.y; + normals[vA + 2] += cb.z; + + normals[vB] += cb.x; + normals[vB + 1] += cb.y; + normals[vB + 2] += cb.z; + + normals[vC] += cb.x; + normals[vC + 1] += cb.y; + normals[vC + 2] += cb.z; + } + + let tempNormal = cc.v3(); + for (let i = 0, l = normals.length; i < l; i+=3) { + tempNormal.x = normals[i]; + tempNormal.y = normals[i+1]; + tempNormal.z = normals[i+2]; + + tempNormal.normalizeSelf(); + + normals[i] = tempNormal.x; + normals[i+1] = tempNormal.y; + normals[i+2] = tempNormal.z; + } + + return normals; +} diff --git a/cocos2d/core/3d/primitive/vertex-data.ts b/cocos2d/core/3d/primitive/vertex-data.ts new file mode 100644 index 00000000000..7cf2597afdd --- /dev/null +++ b/cocos2d/core/3d/primitive/vertex-data.ts @@ -0,0 +1,52 @@ +import Vec3 from '../../value-types/vec3'; + +/** + * @class primitive.VertexData + * @param {number[]} positions + * @param {number[]} normals + * @param {number[]} uvs + * @param {number[]} indices + * @param {Vec3} minPos + * @param {Vec3} maxPos + * @param {number} boundingRadius + */ +export default class VertexData { + /** + * @property {number[]} positions + */ + positions: number[]; + /** + * @property {number[]} normals + */ + normals: number[]; + /** + * @property {number[]} uvs + */ + uvs: number[]; + /** + * @property {[Number]} indices + */ + indices: number[]; + /** + * @property {Vec3} minPos + */ + minPos: Vec3; + /** + * @property {Vec3} maxPos + */ + maxPos: Vec3; + /** + * @property {number} boundingRadius + */ + boundingRadius: number; + + constructor(positions: number[], normals: number[], uvs: number[], indices: number[], minPos: Vec3, maxPos: Vec3, boundingRadius: number) { + this.positions = positions; + this.normals = normals; + this.uvs = uvs; + this.indices = indices; + this.minPos = minPos; + this.maxPos = maxPos; + this.boundingRadius = boundingRadius; + } +} diff --git a/cocos2d/core/3d/skeleton/CCJointMatrixCurve.js b/cocos2d/core/3d/skeleton/CCJointMatrixCurve.js new file mode 100644 index 00000000000..3308c8c6804 --- /dev/null +++ b/cocos2d/core/3d/skeleton/CCJointMatrixCurve.js @@ -0,0 +1,23 @@ +const { DynamicAnimCurve, quickFindIndex } = require('../../../animation/animation-curves'); + +let JointMatrixCurve = cc.Class({ + name: 'cc.JointMatrixCurve', + extends: DynamicAnimCurve, + + _findFrameIndex: quickFindIndex, + sample (time, ratio) { + let ratios = this.ratios; + let index = this._findFrameIndex(ratios, ratio); + if (index < -1) { + index = ~index - 1; + } + + let pairs = this.pairs; + for (let i = 0; i < pairs.length; i++) { + let pair = pairs[i]; + pair.target._jointMatrix = pair.values[index]; + } + } +}); + +module.exports = JointMatrixCurve; diff --git a/cocos2d/core/3d/CCSkeletonAnimationClip.js b/cocos2d/core/3d/skeleton/CCSkeleton.js similarity index 55% rename from cocos2d/core/3d/CCSkeletonAnimationClip.js rename to cocos2d/core/3d/skeleton/CCSkeleton.js index d9e59c1b3a6..bfb48c012fe 100644 --- a/cocos2d/core/3d/CCSkeletonAnimationClip.js +++ b/cocos2d/core/3d/skeleton/CCSkeleton.js @@ -23,67 +23,62 @@ THE SOFTWARE. ****************************************************************************/ -const Model = require('./CCModel'); - /** - * @module cc - */ -/** - * !#en SkeletonAnimationClip Asset. - * !#zh 骨骼动画剪辑。 - * @class SkeletonAnimationClip - * @extends AnimationClip - */ -var SkeletonAnimationClip = cc.Class({ - name: 'cc.SkeletonAnimationClip', - extends: cc.AnimationClip, +let Skeleton = cc.Class({ + name: 'cc.Skeleton', + extends: cc.Asset, + + ctor () { + this.loaded = false; + this._bindposes = []; + this._uniqueBindPoses = []; + this._jointPaths = []; + }, properties: { - _modelSetter: { - set: function (model) { - this._model = model; + _model: cc.Model, + _jointIndices: [], + _skinIndex: -1, + + jointPaths: { + get () { + return this._jointPaths; + } + }, + bindposes: { + get () { + return this._bindposes; + } + }, + uniqueBindPoses: { + get () { + return this._uniqueBindPoses; } }, - model: { get () { return this._model; - }, - - type: Model - } - }, - - ctor () { - this._modelUuid = ''; - this._animationID = -1; - this._model = null; - this._inited = false; - }, - - init () { - if (this._inited) return; - this._inited = true; - this._model.initAnimationClip(this); - }, - - _serialize: CC_EDITOR && function () { - return { - modelUuid: this._modelUuid, - animationID: this._animationID, - name: this._name, + } } }, - _deserialize (data, handle) { - this._modelUuid = data.modelUuid; - this._animationID = data.animationID; - this._name = data.name; - - if (this._modelUuid) { - handle.result.push(this, '_modelSetter', this._modelUuid); + onLoad () { + let nodes = this._model.nodes; + let jointIndices = this._jointIndices; + let jointPaths = this._jointPaths; + let bindposes = this._bindposes; + let uniqueBindPoses = this._uniqueBindPoses; + for (let i = 0; i < jointIndices.length; i++) { + let node = nodes[jointIndices[i]]; + jointPaths[i] = node.path; + if (node.uniqueBindPose) { + bindposes[i] = uniqueBindPoses[i] = node.uniqueBindPose; + } + else { + bindposes[i] = node.bindpose[this._skinIndex]; + } } } }); -cc.SkeletonAnimationClip = module.exports = SkeletonAnimationClip; +cc.Skeleton = module.exports = Skeleton; \ No newline at end of file diff --git a/cocos2d/core/3d/CCSkeletonAnimation.js b/cocos2d/core/3d/skeleton/CCSkeletonAnimation.js similarity index 78% rename from cocos2d/core/3d/CCSkeletonAnimation.js rename to cocos2d/core/3d/skeleton/CCSkeletonAnimation.js index 318020a2892..fc7f5c2279c 100644 --- a/cocos2d/core/3d/CCSkeletonAnimation.js +++ b/cocos2d/core/3d/skeleton/CCSkeletonAnimation.js @@ -23,10 +23,9 @@ THE SOFTWARE. ****************************************************************************/ -const Animation = require('../components/CCAnimation'); -const Model = require('./CCModel'); +const Animation = require('../../components/CCAnimation'); +const Model = require('../CCModel'); const SkeletonAnimationClip = require('./CCSkeletonAnimationClip'); -const AnimationClip = require('../../animation/animation-clip'); /** * @module cc @@ -37,17 +36,20 @@ const AnimationClip = require('../../animation/animation-clip'); * @class SkeletonAnimation * @extends Animation */ -var SkeletonAnimation = cc.Class({ +let SkeletonAnimation = cc.Class({ name: 'cc.SkeletonAnimation', extends: Animation, editor: CC_EDITOR && { inspector: 'packages://inspector/inspectors/comps/skeleton-animation.js', + menu: 'i18n:MAIN_MENU.component.others/Skeleton Animation', }, properties: { - _model: Model, - _nodes: [cc.Node], + _model: { + default: null, + type: Model + }, _defaultClip: { override: true, @@ -77,18 +79,40 @@ var SkeletonAnimation = cc.Class({ get () { return this._model; }, - type: Model + set (val) { + this._model = val; + this._updateClipModel(); + }, + type: Model, }, }, - getAnimationState (name) { - let state = this._super(name); - let clip = state.clip; - clip.init(this._nodes); - return state; + __preload () { + this._updateClipModel(); + }, + + _updateClipModel () { + if (this._defaultClip) { + this._defaultClip._model = this._model; + } + + let clips = this._clips; + for (let i = 0; i < clips.length; i++) { + clips[i]._model = this._model; + } + }, + + addClip (clip, newName) { + clip._model = this._model; + return Animation.prototype.addClip.call(this, clip, newName); }, searchClips: CC_EDITOR && function () { + if (!this._model) { + cc.warn('There was no model provided.'); + return; + } + this._clips.length = 0; let self = this; Editor.assetdb.queryPathByUuid(this._model._uuid, function (err, modelPath) { diff --git a/cocos2d/core/3d/skeleton/CCSkeletonAnimationClip.js b/cocos2d/core/3d/skeleton/CCSkeletonAnimationClip.js new file mode 100644 index 00000000000..31ba9bbf084 --- /dev/null +++ b/cocos2d/core/3d/skeleton/CCSkeletonAnimationClip.js @@ -0,0 +1,266 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import Mat4 from '../../value-types/mat4'; + +const AnimationClip = require('../../../animation/animation-clip'); +const JointMatrixCurve = require('./CCJointMatrixCurve'); + +function maxtrixToArray (matrix) { + let data = new Float32Array(16); + data.set(matrix.m); + return data; +} + +/** +* @module cc +*/ +/** + * !#en SkeletonAnimationClip Asset. + * !#zh 骨骼动画剪辑。 + * @class SkeletonAnimationClip + * @extends AnimationClip + */ +let SkeletonAnimationClip = cc.Class({ + name: 'cc.SkeletonAnimationClip', + extends: AnimationClip, + + properties: { + _nativeAsset: { + override: true, + get () { + return this._buffer; + }, + set (bin) { + let buffer = ArrayBuffer.isView(bin) ? bin.buffer : bin; + this._buffer = new Float32Array(buffer || bin, 0, buffer.byteLength / 4); + } + }, + + /** + * Describe the data structure. + * { path: { offset, frameCount, property } } + */ + description: { + default: null, + type: Object, + }, + + /** + * SkeletonAnimationClip's curveData is generated from binary buffer. + * So should not serialize curveData. + */ + curveData: { + visible: false, + override: true, + get () { + return this._curveData || {}; + }, + set () {} + } + }, + + statics: { + preventDeferredLoadDependents: true, + }, + + _init () { + if (this._curveData) { + return this._curveData; + } + + this._curveData = {}; + + this._generateCommonCurve(); + + if (this._model.precomputeJointMatrix) { + this._generateJointMatrixCurve(); + } + + return this._curveData; + }, + + _generateCommonCurve () { + let buffer = this._buffer; + let description = this.description; + + let offset = 0; + function getValue () { + return buffer[offset++]; + } + + if (!this._curveData.paths) { + this._curveData.paths = {}; + } + let paths = this._curveData.paths; + + for (let path in description) { + let des = description[path]; + let curves = {}; + paths[path] = { props: curves }; + + for (let property in des) { + let frames = []; + + let frameCount = des[property].frameCount; + offset = des[property].offset; + for (let i = 0; i < frameCount; i++) { + let frame = getValue(); + let value; + if (property === 'position' || property === 'scale') { + value = cc.v3(getValue(), getValue(), getValue()); + } + else if (property === 'quat') { + value = cc.quat(getValue(), getValue(), getValue(), getValue()); + } + frames.push({ frame, value }); + } + + curves[property] = frames; + } + } + }, + + _generateJointMatrixCurve () { + let rootNode = this._model.rootNode; + let curveData = this._curveData; + let paths = curveData.paths; + + let newCurveData = { ratios: [], jointMatrixMap: {} }; + let jointMatrixMap = newCurveData.jointMatrixMap; + + // walk through node tree to calculate node's joint matrix at time. + function walk (node, time, pm) { + let matrix; + let EPSILON = 10e-5; + + let path = paths[node.path]; + if (node !== rootNode && path) { + let props = path.props; + for (let prop in props) { + let frames = props[prop]; + for (let i = 0; i < frames.length; i++) { + let end = frames[i]; + + if (Math.abs(end.frame - time) < EPSILON) { + node[prop].set(end.value); + break; + } + else if (end.frame > time) { + let start = frames[i - 1]; + let ratio = (time - start.frame) / (end.frame - start.frame); + start.value.lerp(end.value, ratio, node[prop]); + break; + } + } + } + + matrix = cc.mat4(); + Mat4.fromRTS(matrix, node.quat, node.position, node.scale); + + if (pm) { + Mat4.mul(matrix, pm, matrix); + } + + if (!props._jointMatrix) { + props._jointMatrix = []; + } + + let bindWorldMatrix; + if (node.uniqueBindPose) { + bindWorldMatrix = cc.mat4(); + Mat4.mul(bindWorldMatrix, matrix, node.uniqueBindPose); + } + + if (!jointMatrixMap[node.path]) { + jointMatrixMap[node.path] = []; + } + + if (bindWorldMatrix) { + jointMatrixMap[node.path].push(maxtrixToArray(bindWorldMatrix)) + } + else { + jointMatrixMap[node.path].push(matrix) + } + } + + let children = node.children; + for (let name in children) { + let child = children[name]; + walk(child, time, matrix); + } + } + + let time = 0; + let duration = this.duration; + let step = 1 / this.sample; + + while (time < duration) { + newCurveData.ratios.push(time / duration); + walk(rootNode, time); + time += step; + } + + this._curveData = newCurveData; + }, + + _createJointMatrixCurve (state, root) { + let curve = new JointMatrixCurve(); + curve.ratios = this.curveData.ratios; + + curve.pairs = []; + + let jointMatrixMap = this.curveData.jointMatrixMap; + for (let path in jointMatrixMap) { + let target = cc.find(path, root); + if (!target) continue; + + curve.pairs.push({ + target: target, + values: jointMatrixMap[path] + }); + } + + return [curve]; + }, + + createCurves (state, root) { + if (!this._model) { + cc.warn(`Skeleton Animation Clip [${this.name}] Can not find model`); + return []; + } + + this._init(); + + if (this._model.precomputeJointMatrix) { + return this._createJointMatrixCurve(state, root); + } + else { + return AnimationClip.prototype.createCurves.call(this, state, root); + } + } +}); + +cc.SkeletonAnimationClip = module.exports = SkeletonAnimationClip; diff --git a/cocos2d/core/3d/skeleton/CCSkinnedMeshRenderer.js b/cocos2d/core/3d/skeleton/CCSkinnedMeshRenderer.js new file mode 100644 index 00000000000..73d6ce59571 --- /dev/null +++ b/cocos2d/core/3d/skeleton/CCSkinnedMeshRenderer.js @@ -0,0 +1,370 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import Mat4 from '../../value-types/mat4'; +const Skeleton = require('./CCSkeleton'); +const MeshRenderer = require('../../mesh/CCMeshRenderer'); +const RenderFlow = require('../../renderer/render-flow'); +const enums = require('../../../renderer/enums'); + +let _m4_tmp = cc.mat4(); +let _m4_tmp2 = cc.mat4(); + +/** + * !#en + * Skinned Mesh Renderer + * !#zh + * 蒙皮渲染组件 + * @class SkinnedMeshRenderer + * @extends MeshRenderer + */ +let SkinnedMeshRenderer = cc.Class({ + name: 'cc.SkinnedMeshRenderer', + extends: MeshRenderer, + + editor: CC_EDITOR && { + menu: 'i18n:MAIN_MENU.component.mesh/Skinned Mesh Renderer', + }, + + ctor () { + this._jointsData = this._jointsFloat32Data = null; + this._jointsTexture = null; + this._joints = []; + this._dummyNode = new cc.Node(); + this._jointsTextureOptions = null; + this._usingRGBA8Texture = false; + }, + + properties: { + _skeleton: Skeleton, + _rootBone: cc.Node, + + /** + * !#en + * Skeleton Asset + * !#zh + * 骨骼资源 + * @property {Skeleton} skeleton + */ + skeleton: { + get () { + return this._skeleton; + }, + set (val) { + this._skeleton = val; + this._init(); + }, + type: Skeleton + }, + + /** + * !#en + * Root Bone + * !#zh + * 骨骼根节点 + * @property {Node} rootBone + */ + rootBone: { + get () { + return this._rootBone; + }, + set (val) { + this._rootBone = val; + this._init(); + }, + type: cc.Node + }, + + // SkinnedMeshRenderer cannot batch + enableAutoBatch: { + get () { + return false; + }, + visible: false, + override: true + } + }, + + __preload () { + this._super(); + this._init(); + }, + + _init () { + this._model = this._skeleton && this._skeleton.model; + this._calFunc = null; + + this._initJoints(); + this._initJointsTexture(); + this._initCalcFunc(); + this._updateRenderNode(); + }, + + _calcWorldMatrixToRoot (joint) { + let worldMatrixToRoot = joint._worldMatrixToRoot; + if (!worldMatrixToRoot) { + joint._worldMatrixToRoot = worldMatrixToRoot = cc.mat4(); + joint.getLocalMatrix(worldMatrixToRoot); + } + else { + return; + } + + let parent = joint.parent; + if (parent !== this.rootBone) { + if (!parent._worldMatrixToRoot) { + this._calcWorldMatrixToRoot(parent); + } + Mat4.mul(worldMatrixToRoot, parent._worldMatrixToRoot, worldMatrixToRoot); + } + }, + + _validateRender () { + if (!this._jointsData) { + this.disableRender(); + return; + } + this._super(); + }, + + _initJoints () { + let joints = this._joints; + joints.length = 0; + + if (!this.skeleton || !this.rootBone) return; + + let useJointMatrix = this._useJointMatrix(); + + let jointPaths = this.skeleton.jointPaths; + let rootBone = this.rootBone; + for (let i = 0; i < jointPaths.length; i++) { + let joint = cc.find(jointPaths[i], rootBone); + if (!joint) { + cc.warn('Can not find joint in root bone [%s] with path [%s]', rootBone.name, jointPaths[i]); + } + + if (useJointMatrix) { + joint._renderFlag &= ~RenderFlow.FLAG_CHILDREN; + this._calcWorldMatrixToRoot(joint); + } + + joints.push(joint); + } + + if (useJointMatrix) { + const uniqueBindPoses = this.skeleton.uniqueBindPoses; + for (let i = 0; i < jointPaths.length; i++) { + let joint = joints[i]; + if (uniqueBindPoses[i]) { + Mat4.mul(_m4_tmp, joint._worldMatrixToRoot, uniqueBindPoses[i]); + joint._jointMatrix = Mat4.toArray([], _m4_tmp); + } + else { + joint._jointMatrix = joint._worldMatrixToRoot; + } + + } + } + }, + + _initJointsTexture () { + if (!this._skeleton) return; + + let jointCount = this._joints.length; + + let inited = false; + if (jointCount <= cc.sys.getMaxJointMatrixSize()) { + inited = true; + + this._jointsData = this._jointsFloat32Data = new Float32Array(jointCount * 16); + } + + if (!inited) { + let SUPPORT_FLOAT_TEXTURE = !!cc.sys.glExtension('OES_texture_float'); + let size; + if (jointCount > 256) { + size = 64; + } else if (jointCount > 64) { + size = 32; + } else if (jointCount > 16) { + size = 16; + } else { + size = 8; + } + + this._jointsData = this._jointsFloat32Data = new Float32Array(size * size * 4); + + let pixelFormat = cc.Texture2D.PixelFormat.RGBA32F, + width = size, + height = size; + + if (!SUPPORT_FLOAT_TEXTURE) { + this._jointsData = new Uint8Array(this._jointsFloat32Data.buffer); + pixelFormat = cc.Texture2D.PixelFormat.RGBA8888; + width *= 4; + + this._usingRGBA8Texture = true; + + cc.warn(`SkinnedMeshRenderer [${this.node.name}] has too many joints [${jointCount}] and device do not support float32 texture, fallback to use RGBA8888 texture, which is much slower.`); + } + + let texture = this._jointsTexture || new cc.Texture2D(); + let NEAREST = cc.Texture2D.Filter.NEAREST; + texture.setFilters(NEAREST, NEAREST); + texture.initWithData(this._jointsData, pixelFormat, width, height); + this._jointsTexture = texture; + this._jointsTextureOptions = { + format: pixelFormat, + width: texture.width, + height: texture.height, + images:[] + }; + } + + this._updateMaterial(); + }, + + _updateMaterial () { + MeshRenderer.prototype._updateMaterial.call(this); + + let materials = this.getMaterials(); + for (let i = 0; i < materials.length; i++) { + let material = materials[i]; + if (this._jointsTexture) { + material.setProperty('jointsTexture', this._jointsTexture); + material.setProperty('jointsTextureSize', new Float32Array([this._jointsTexture.width, this._jointsTexture.height])); + + material.define('CC_JOINTS_TEXTURE_FLOAT32', !!cc.sys.glExtension('OES_texture_float')); + material.define('CC_USE_JOINTS_TEXTRUE', true); + } + else { + if (this._jointsFloat32Data) { + material.setProperty('jointMatrices', this._jointsFloat32Data, undefined, true); + } + material.define('CC_USE_JOINTS_TEXTRUE', false); + } + material.define('CC_USE_SKINNING', true); + } + }, + + _setJointsDataWithArray (iMatrix, matrixArray) { + let data = this._jointsFloat32Data; + data.set(matrixArray, iMatrix * 16); + }, + + _setJointsDataWithMatrix (iMatrix, matrix) { + this._jointsFloat32Data.set(matrix.m, 16 * iMatrix); + }, + + _commitJointsData () { + if (this._jointsTexture) { + this._jointsTextureOptions.images[0] = this._jointsData; + this._jointsTexture.update(this._jointsTextureOptions); + } + }, + + _useJointMatrix () { + return this._model && this._model.precomputeJointMatrix; + }, + + _updateRenderNode () { + if (this._useJointMatrix() || this._usingRGBA8Texture) { + this._assembler.setRenderNode(this.rootBone) + } else { + this._assembler.setRenderNode(this._dummyNode); + } + }, + + _initCalcFunc () { + if (this._useJointMatrix()) { + this._calFunc = this._calJointMatrix; + } + else if (this._usingRGBA8Texture) { + this._calFunc = this._calRGBA8WorldMatrix; + } + else { + this._calFunc = this._calWorldMatrix; + } + }, + + _calJointMatrix () { + const joints = this._joints; + const bindposes = this.skeleton.bindposes; + const uniqueBindPoses = this.skeleton.uniqueBindPoses; + for (let i = 0; i < joints.length; ++i) { + let joint = joints[i]; + let jointMatrix = joint._jointMatrix; + + if (uniqueBindPoses[i]) { + this._setJointsDataWithArray(i, jointMatrix); + } + else { + Mat4.multiply(_m4_tmp, jointMatrix, bindposes[i]); + this._setJointsDataWithMatrix(i, _m4_tmp); + } + } + }, + + // Some device rgba8 texture precision is low, when encode a big number it may loss precision. + // Invert root bone matrix can effectively avoid big position encode into rgba8 texture. + _calRGBA8WorldMatrix () { + const joints = this._joints; + const bindposes = this.skeleton.bindposes; + + this.rootBone._updateWorldMatrix(); + let rootMatrix = this.rootBone._worldMatrix; + let invRootMat = Mat4.invert(_m4_tmp2, rootMatrix); + + for (let i = 0; i < joints.length; ++i) { + let joint = joints[i]; + joint._updateWorldMatrix(); + + Mat4.multiply(_m4_tmp, invRootMat, joint._worldMatrix); + Mat4.multiply(_m4_tmp, _m4_tmp, bindposes[i]); + this._setJointsDataWithMatrix(i, _m4_tmp); + } + }, + + _calWorldMatrix () { + const joints = this._joints; + const bindposes = this.skeleton.bindposes; + for (let i = 0; i < joints.length; ++i) { + let joint = joints[i]; + + joint._updateWorldMatrix(); + Mat4.multiply(_m4_tmp, joint._worldMatrix, bindposes[i]); + this._setJointsDataWithMatrix(i, _m4_tmp); + } + }, + + calcJointMatrix () { + if (!this.skeleton || !this.rootBone) return; + + this._calFunc.call(this); + this._commitJointsData(); + } +}); + +cc.SkinnedMeshRenderer = module.exports = SkinnedMeshRenderer; diff --git a/cocos2d/core/value-types/index.js b/cocos2d/core/3d/skeleton/skinned-mesh-renderer.js similarity index 74% rename from cocos2d/core/value-types/index.js rename to cocos2d/core/3d/skeleton/skinned-mesh-renderer.js index 4e44fac8c0f..e5e70288c83 100644 --- a/cocos2d/core/value-types/index.js +++ b/cocos2d/core/3d/skeleton/skinned-mesh-renderer.js @@ -1,5 +1,4 @@ /**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. http://www.cocos.com @@ -24,13 +23,15 @@ THE SOFTWARE. ****************************************************************************/ -require('./value-type'); -require('./vec2'); -require('./vec3'); -require('./quat'); -require('./mat4'); -require('./size'); -require('./rect'); -require('./color'); +const SkinnedMeshRenderer = require('./CCSkinnedMeshRenderer'); +const MeshRendererAssembler = require('../../mesh/mesh-renderer'); +const RenderFlow = require('../../renderer/render-flow'); -cc.vmath = require('../renderer/render-engine').math; +export default class SkinnedMeshRendererAssembler extends MeshRendererAssembler { + fillBuffers (comp, renderer) { + comp.calcJointMatrix(); + super.fillBuffers(comp, renderer); + } +} + +cc.Assembler.register(SkinnedMeshRenderer, SkinnedMeshRendererAssembler); diff --git a/cocos2d/core/CCDebug.js b/cocos2d/core/CCDebug.js index 484cf48f62a..a82010f990b 100644 --- a/cocos2d/core/CCDebug.js +++ b/cocos2d/core/CCDebug.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -23,6 +23,7 @@ THE SOFTWARE. ****************************************************************************/ +const utils = require('./platform/utils'); const debugInfos = require('../../DebugInfos') || {}; const ERROR_MAP_URL = 'https://github.com/cocos-creator/engine/blob/master/EngineErrorMap.md'; @@ -33,7 +34,7 @@ let logList; * @module cc */ -cc.log = cc.warn = cc.error = cc.assert = console.log; +cc.log = cc.warn = cc.error = cc.assert = console.log.bind ? console.log.bind(console) : console.log; let resetDebugSetting = function (mode) { // reset @@ -129,7 +130,7 @@ let resetDebugSetting = function (mode) { cc.error = console.error.bind(console); } else { - cc.error = CC_JSB ? console.error : function () { + cc.error = CC_JSB || CC_RUNTIME ? console.error : function () { return console.error.apply(console, arguments); }; } @@ -172,7 +173,7 @@ let resetDebugSetting = function (mode) { cc.warn = console.warn.bind(console); } else { - cc.warn = CC_JSB ? console.warn : function () { + cc.warn = CC_JSB || CC_RUNTIME ? console.warn : function () { return console.warn.apply(console, arguments); }; } @@ -188,7 +189,7 @@ let resetDebugSetting = function (mode) { * @param {String|any} msg - A JavaScript string containing zero or more substitution strings. * @param {any} ...subst - JavaScript objects with which to replace substitution strings within msg. This gives you additional control over the format of the output. */ - if (CC_JSB) { + if (CC_JSB || CC_RUNTIME) { if (scriptEngineType === "JavaScriptCore") { // console.log has to use `console` as its context for iOS 8~9. Therefore, apply it. cc.log = function () { @@ -211,13 +212,9 @@ let resetDebugSetting = function (mode) { }; cc._throw = CC_EDITOR ? Editor.error : function (error) { - var stack = error.stack; - if (stack) { - cc.error(CC_JSB ? (error + '\n' + stack) : stack); - } - else { - cc.error(error); - } + utils.callInNextTick(function () { + throw error; + }); }; function getTypedFormatter (type) { @@ -343,7 +340,7 @@ module.exports = cc.debug = { * !#en Gets error message with the error id and possible parameters. * !#zh 通过 error id 和必要的参数来获取错误信息。 * @method getError - * @param {id} errorId + * @param {String} errorId * @param {any} [param] * @return {String} */ @@ -366,7 +363,7 @@ module.exports = cc.debug = { * @param {Boolean} displayStats */ setDisplayStats: function (displayStats) { - if (cc.profiler) { + if (cc.profiler && cc.game.renderType !== cc.game.RENDER_TYPE_CANVAS) { displayStats ? cc.profiler.showStats() : cc.profiler.hideStats(); cc.game.config.showFPS = !!displayStats; } diff --git a/cocos2d/core/CCDirector.js b/cocos2d/core/CCDirector.js index cf4f0cff2dd..34cace47e28 100644 --- a/cocos2d/core/CCDirector.js +++ b/cocos2d/core/CCDirector.js @@ -26,7 +26,6 @@ ****************************************************************************/ const EventTarget = require('./event/event-target'); -const AutoReleaseUtils = require('./load-pipeline/auto-release-utils'); const ComponentScheduler = require('./component-scheduler'); const NodeActivator = require('./node-activator'); const Obj = require('./platform/CCObject'); @@ -108,7 +107,6 @@ const Scheduler = require('./CCScheduler'); cc.Director = function () { EventTarget.call(this); - this.invalid = false; // paused? this._paused = false; // purge? @@ -117,13 +115,17 @@ cc.Director = function () { this._winSizeInPoints = null; // scenes - this._loadingScene = ''; this._scene = null; + this._loadingScene = ''; // FPS this._totalFrames = 0; this._lastUpdate = 0; this._deltaTime = 0.0; + this._startTime = 0.0; + + // ParticleSystem max step delta time + this._maxParticleDeltaTime = 0.0; // Scheduler for user registration update this._scheduler = null; @@ -147,6 +149,7 @@ cc.Director.prototype = { init: function () { this._totalFrames = 0; this._lastUpdate = performance.now(); + this._startTime = this._lastUpdate; this._paused = false; this._purgeDirectorInNextLoop = false; this._winSizeInPoints = cc.size(0, 0); @@ -203,21 +206,29 @@ cc.Director.prototype = { this._physicsManager = null; } + // physics 3d manager + if (cc.Physics3DManager && (CC_PHYSICS_BUILTIN || CC_PHYSICS_CANNON)) { + this._physics3DManager = new cc.Physics3DManager(); + this._scheduler.scheduleUpdate(this._physics3DManager, Scheduler.PRIORITY_SYSTEM, false); + } else { + this._physics3DManager = null; + } + // WidgetManager if (cc._widgetManager) { cc._widgetManager.init(this); } - - cc.loader.init(this); }, /** * calculates delta time since last time it was called */ - calculateDeltaTime: function () { - var now = performance.now(); + calculateDeltaTime: function (now) { + if (!now) now = performance.now(); - this._deltaTime = (now - this._lastUpdate) / 1000; + // avoid delta time from being negative + // negative deltaTime would be caused by the precision of now's value, for details please see: https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame + this._deltaTime = now > this._lastUpdate ? (now - this._lastUpdate) / 1000 : 0; if (CC_DEBUG && (this._deltaTime > 1)) this._deltaTime = 1 / 60.0; @@ -333,7 +344,7 @@ cc.Director.prototype = { * @deprecated since v2.0 */ purgeCachedData: function () { - cc.loader.releaseAll(); + cc.assetManager.releaseAll(); }, /** @@ -350,19 +361,20 @@ cc.Director.prototype = { if (eventManager) eventManager.setEnabled(false); - cc.renderer.clear(); - if (!CC_EDITOR) { if (cc.isValid(this._scene)) { this._scene.destroy(); } this._scene = null; + + cc.renderer.clear(); + cc.assetManager.builtins.clear(); } - this.stopAnimation(); + cc.game.pause(); // Clear all caches - cc.loader.releaseAll(); + cc.assetManager.releaseAll(); }, /** @@ -394,7 +406,7 @@ cc.Director.prototype = { this._scheduler.scheduleUpdate(this._physicsManager, cc.Scheduler.PRIORITY_SYSTEM, false); } - this.startAnimation(); + cc.game.resume(); }, /** @@ -403,12 +415,14 @@ cc.Director.prototype = { * The new scene will be launched immediately. * !#zh 立刻切换指定场景。 * @method runSceneImmediate - * @param {Scene} scene - The need run scene. + * @param {Scene|SceneAsset} scene - The need run scene. * @param {Function} [onBeforeLoadScene] - The function invoked at the scene before loading. * @param {Function} [onLaunched] - The function invoked at the scene after launch. */ runSceneImmediate: function (scene, onBeforeLoadScene, onLaunched) { - cc.assertID(scene instanceof cc.Scene, 1216); + cc.assertID(scene instanceof cc.Scene || scene instanceof cc.SceneAsset, 1216); + + if (scene instanceof cc.SceneAsset) scene = scene.scene; CC_BUILD && CC_DEBUG && console.time('InitScene'); scene._load(); // ensure scene initialized @@ -438,8 +452,7 @@ cc.Director.prototype = { if (!CC_EDITOR) { // auto release assets CC_BUILD && CC_DEBUG && console.time('AutoRelease'); - var autoReleaseAssets = oldScene && oldScene.autoReleaseAssets && oldScene.dependAssets; - AutoReleaseUtils.autoRelease(autoReleaseAssets, scene.dependAssets, persistNodeList); + cc.assetManager._releaseManager._autoRelease(oldScene, scene, persistNodeList); CC_BUILD && CC_DEBUG && console.timeEnd('AutoRelease'); } @@ -468,7 +481,7 @@ cc.Director.prototype = { CC_BUILD && CC_DEBUG && console.timeEnd('Activate'); //start scene - this.startAnimation(); + cc.game.resume(); if (onLaunched) { onLaunched(null, scene); @@ -482,57 +495,24 @@ cc.Director.prototype = { * The new scene will be launched at the end of the current frame. * !#zh 运行指定场景。 * @method runScene - * @param {Scene} scene - The need run scene. + * @param {Scene|SceneAsset} scene - The need run scene. * @param {Function} [onBeforeLoadScene] - The function invoked at the scene before loading. * @param {Function} [onLaunched] - The function invoked at the scene after launch. - * @private */ runScene: function (scene, onBeforeLoadScene, onLaunched) { cc.assertID(scene, 1205); - cc.assertID(scene instanceof cc.Scene, 1216); + cc.assertID(scene instanceof cc.Scene || scene instanceof cc.SceneAsset, 1216); + if (scene instanceof cc.SceneAsset) scene = scene.scene; // ensure scene initialized scene._load(); // Delay run / replace scene to the end of the frame - this.once(cc.Director.EVENT_AFTER_UPDATE, function () { + this.once(cc.Director.EVENT_AFTER_DRAW, function () { this.runSceneImmediate(scene, onBeforeLoadScene, onLaunched); }, this); }, - // @Scene loading section - - _getSceneUuid: function (key) { - var scenes = game._sceneInfos; - if (typeof key === 'string') { - if (!key.endsWith('.fire')) { - key += '.fire'; - } - if (key[0] !== '/' && !key.startsWith('db://')) { - key = '/' + key; // 使用全名匹配 - } - // search scene - for (var i = 0; i < scenes.length; i++) { - var info = scenes[i]; - if (info.url.endsWith(key)) { - return info; - } - } - } - else if (typeof key === 'number') { - if (0 <= key && key < scenes.length) { - return scenes[key]; - } - else { - cc.errorID(1206, key); - } - } - else { - cc.errorID(1207, key); - } - return null; - }, - /** * !#en Loads the scene by its name. * !#zh 通过场景名称进行加载场景。 @@ -544,15 +524,29 @@ cc.Director.prototype = { */ loadScene: function (sceneName, onLaunched, _onUnloaded) { if (this._loadingScene) { - cc.errorID(1208, sceneName, this._loadingScene); + cc.warnID(1208, sceneName, this._loadingScene); return false; } - var info = this._getSceneUuid(sceneName); - if (info) { - var uuid = info.uuid; + var bundle = cc.assetManager.bundles.find(function (bundle) { + return bundle.getSceneInfo(sceneName); + }); + if (bundle) { this.emit(cc.Director.EVENT_BEFORE_SCENE_LOADING, sceneName); this._loadingScene = sceneName; - this._loadSceneByUuid(uuid, onLaunched, _onUnloaded); + var self = this; + console.time('LoadScene ' + sceneName); + bundle.loadScene(sceneName, function (err, scene) { + console.timeEnd('LoadScene ' + sceneName); + self._loadingScene = ''; + if (err) { + err = 'Failed to load scene: ' + err; + cc.error(err); + onLaunched && onLaunched(err); + } + else { + self.runSceneImmediate(scene, _onUnloaded, onLaunched); + } + }); return true; } else { @@ -561,7 +555,7 @@ cc.Director.prototype = { } }, - /** + /** * !#en * Preloads the scene to reduces loading time. You can call this method at any time you want. * After calling this method, you still need to launch the scene by `cc.director.loadScene`. @@ -580,95 +574,19 @@ cc.Director.prototype = { * @param {Function} [onLoaded] - callback, will be called after scene loaded. * @param {Error} onLoaded.error - null or the error object. */ - preloadScene: function (sceneName, onProgress, onLoaded) { - if (onLoaded === undefined) { - onLoaded = onProgress; - onProgress = null; - } - - var info = this._getSceneUuid(sceneName); - if (info) { - this.emit(cc.Director.EVENT_BEFORE_SCENE_LOADING, sceneName); - cc.loader.load({ uuid: info.uuid, type: 'uuid' }, - onProgress, - function (error, asset) { - if (error) { - cc.errorID(1210, sceneName, error.message); - } - if (onLoaded) { - onLoaded(error, asset); - } - }); + preloadScene (sceneName, onProgress, onLoaded) { + var bundle = cc.assetManager.bundles.find(function (bundle) { + return bundle.getSceneInfo(sceneName); + }); + if (bundle) { + bundle.preloadScene(sceneName, null, onProgress, onLoaded); } else { - var error = 'Can not preload the scene "' + sceneName + '" because it is not in the build settings.'; - onLoaded(new Error(error)); - cc.error('preloadScene: ' + error); + cc.errorID(1209, sceneName); + return null; } }, - /** - * Loads the scene by its uuid. - * @method _loadSceneByUuid - * @param {String} uuid - the uuid of the scene asset to load - * @param {Function} [onLaunched] - * @param {Function} [onUnloaded] - * @param {Boolean} [dontRunScene] - Just download and initialize the scene but will not launch it, - * only take effect in the Editor. - * @private - */ - _loadSceneByUuid: function (uuid, onLaunched, onUnloaded, dontRunScene) { - if (CC_EDITOR) { - if (typeof onLaunched === 'boolean') { - dontRunScene = onLaunched; - onLaunched = null; - } - if (typeof onUnloaded === 'boolean') { - dontRunScene = onUnloaded; - onUnloaded = null; - } - } - //cc.AssetLibrary.unloadAsset(uuid); // force reload - console.time('LoadScene ' + uuid); - cc.AssetLibrary.loadAsset(uuid, function (error, sceneAsset) { - console.timeEnd('LoadScene ' + uuid); - var self = cc.director; - self._loadingScene = ''; - if (error) { - error = 'Failed to load scene: ' + error; - cc.error(error); - } - else { - if (sceneAsset instanceof cc.SceneAsset) { - var scene = sceneAsset.scene; - scene._id = sceneAsset._uuid; - scene._name = sceneAsset._name; - if (CC_EDITOR) { - if (!dontRunScene) { - self.runSceneImmediate(scene, onUnloaded, onLaunched); - } - else { - scene._load(); - if (onLaunched) { - onLaunched(null, scene); - } - } - } - else { - self.runSceneImmediate(scene, onUnloaded, onLaunched); - } - return; - } - else { - error = 'The asset ' + uuid + ' is not a scene'; - cc.error(error); - } - } - if (onLaunched) { - onLaunched(error); - } - }); - }, /** * !#en Resume game logic execution after pause, if the current scene is not paused, nothing will happen. @@ -780,6 +698,16 @@ cc.Director.prototype = { return this._deltaTime; }, + /** + * !#en Returns the total passed time since game start, unit: ms + * !#zh 获取从游戏开始到现在总共经过的时间,单位为 ms + * @method getTotalTime + * @return {Number} + */ + getTotalTime: function () { + return performance.now() - this._startTime; + }, + /** * !#en Returns how many frames were called since the director started. * !#zh 获取 director 启动以来游戏运行的总帧数。 @@ -877,20 +805,36 @@ cc.Director.prototype = { return this._physicsManager; }, + /** + * !#en Returns the cc.Physics3DManager associated with this director. + * !#zh 返回与 director 相关联的 cc.Physics3DManager (物理管理器)。 + * @method getPhysics3DManager + * @return {Physics3DManager} + */ + getPhysics3DManager: function () { + return this._physics3DManager; + }, + // Loop management /* * Starts Animation + * @deprecated since v2.1.2 */ startAnimation: function () { - this.invalid = false; - this._lastUpdate = performance.now(); + cc.game.resume(); }, /* * Stops animation + * @deprecated since v2.1.2 */ stopAnimation: function () { - this.invalid = true; + cc.game.pause(); + }, + + _resetDeltaTime () { + this._lastUpdate = performance.now(); + this._deltaTime = 0; }, /* @@ -917,42 +861,48 @@ cc.Director.prototype = { // Render this.emit(cc.Director.EVENT_BEFORE_DRAW); - renderer.render(this._scene); + renderer.render(this._scene, deltaTime); // After draw this.emit(cc.Director.EVENT_AFTER_DRAW); this._totalFrames++; - } : function () { + } : function (now) { if (this._purgeDirectorInNextLoop) { this._purgeDirectorInNextLoop = false; this.purgeDirector(); } - else if (!this.invalid) { + else { // calculate "global" dt - this.calculateDeltaTime(); + this.calculateDeltaTime(now); // Update if (!this._paused) { + // before update this.emit(cc.Director.EVENT_BEFORE_UPDATE); + // Call start for new added components this._compScheduler.startPhase(); + // Update for components this._compScheduler.updatePhase(this._deltaTime); // Engine update with scheduler this._scheduler.update(this._deltaTime); + // Late update for components this._compScheduler.lateUpdatePhase(this._deltaTime); + // User can use this event to do things after update this.emit(cc.Director.EVENT_AFTER_UPDATE); + // Destroy entities that have been removed recently Obj._deferredDestroy(); } // Render this.emit(cc.Director.EVENT_BEFORE_DRAW); - renderer.render(this._scene); + renderer.render(this._scene, this._deltaTime); // After draw this.emit(cc.Director.EVENT_AFTER_DRAW); @@ -963,11 +913,11 @@ cc.Director.prototype = { }, __fastOn: function (type, callback, target) { - this.add(type, callback, target); + this.on(type, callback, target); }, __fastOff: function (type, callback, target) { - this.remove(type, callback, target); + this.off(type, callback, target); }, }; @@ -1147,6 +1097,22 @@ cc.Director.PROJECTION_CUSTOM = 3; */ cc.Director.PROJECTION_DEFAULT = cc.Director.PROJECTION_2D; +/** + * The event which will be triggered before the physics process.
+ * 物理过程之前所触发的事件。 + * @event Director.EVENT_BEFORE_PHYSICS + * @readonly + */ +cc.Director.EVENT_BEFORE_PHYSICS = 'director_before_physics'; + +/** + * The event which will be triggered after the physics process.
+ * 物理过程之后所触发的事件。 + * @event Director.EVENT_AFTER_PHYSICS + * @readonly + */ +cc.Director.EVENT_AFTER_PHYSICS = 'director_after_physics'; + /** * @module cc */ diff --git a/cocos2d/core/CCGame.js b/cocos2d/core/CCGame.js index 3198b034780..adcfbda8831 100644 --- a/cocos2d/core/CCGame.js +++ b/cocos2d/core/CCGame.js @@ -2,7 +2,7 @@ Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -28,7 +28,6 @@ var EventTarget = require('./event/event-target'); require('../audio/CCAudioEngine'); const debug = require('./CCDebug'); const renderer = require('./renderer/index.js'); -const inputManager = CC_QQPLAY ? require('./platform/BKInputManager') : require('./platform/CCInputManager'); const dynamicAtlasManager = require('../core/renderer/utils/dynamic-atlas/manager'); /** @@ -38,8 +37,7 @@ const dynamicAtlasManager = require('../core/renderer/utils/dynamic-atlas/manage /** * !#en An object to boot the game. * !#zh 包含游戏主体信息并负责驱动游戏的游戏对象。 - * @class game - * @static + * @class Game * @extends EventTarget */ var game = { @@ -61,7 +59,7 @@ var game = { EVENT_HIDE: "game_on_hide", /** - * Event triggered when game back to foreground + * !#en Event triggered when game back to foreground * Please note that this event is not 100% guaranteed to be fired on Web platform, * on native platforms, it corresponds to enter foreground event. * !#zh 游戏进入前台运行时触发的事件。 @@ -73,6 +71,15 @@ var game = { */ EVENT_SHOW: "game_on_show", + /** + * !#en Event triggered when game restart + * !#zh 调用restart后,触发事件。 + * @property EVENT_RESTART + * @constant + * @type {String} + */ + EVENT_RESTART: "game_on_restart", + /** * Event triggered after game inited, at this point all engine objects and game scripts are loaded * @property EVENT_GAME_INITED @@ -130,9 +137,6 @@ var game = { _lastTime: null, _frameTime: null, - // Scenes list - _sceneInfos: [], - /** * !#en The outer frame of the game canvas, parent of game container. * !#zh 游戏画布的外框,container 的父容器。 @@ -188,8 +192,6 @@ var game = { * 0 - Automatically chosen by engine
* 1 - Forced to use canvas renderer
* 2 - Forced to use WebGL renderer, but this will be ignored on mobile browsers
- * 7. scenes
- * "scenes" include available scenes in the current bundle.
*
* Please DO NOT modify this object directly, it won't have any effect.
* !#zh @@ -216,8 +218,6 @@ var game = { * 0 - 通过引擎自动选择。
* 1 - 强制使用 canvas 渲染。 * 2 - 强制使用 WebGL 渲染,但是在部分 Android 浏览器中这个选项会被忽略。
- * 7. scenes
- * “scenes” 当前包中可用场景。
*
* 注意:请不要直接修改这个对象,它不会有任何效果。 * @property config @@ -305,6 +305,7 @@ var game = { if (cc.audioEngine) { cc.audioEngine._restore(); } + cc.director._resetDeltaTime(); // Resume main loop this._runMainLoop(); }, @@ -334,15 +335,18 @@ var game = { cc.director.getScene().destroy(); cc.Object._deferredDestroy(); - cc.director.purgeDirector(); - // Clean up audio if (cc.audioEngine) { cc.audioEngine.uncacheAll(); } cc.director.reset(); - game.onStart(); + + game.pause(); + cc.assetManager.builtins.init(() => { + game.onStart(); + game.emit(game.EVENT_RESTART); + }); }); }, @@ -371,20 +375,29 @@ var game = { this.emit(this.EVENT_ENGINE_INITED); }, - _prepareFinished (cb) { - this._prepared = true; + _loadPreviewScript (cb) { + if (CC_PREVIEW && window.__quick_compile_project__) { + window.__quick_compile_project__.load(cb); + } + else { + cb(); + } + }, + _prepareFinished (cb) { // Init engine this._initEngine(); - // Log engine version - console.log('Cocos Creator v' + cc.ENGINE_VERSION); - this._setAnimFrame(); - this._runMainLoop(); + cc.assetManager.builtins.init(() => { + // Log engine version + console.log('Cocos Creator v' + cc.ENGINE_VERSION); + this._prepared = true; + this._runMainLoop(); - this.emit(this.EVENT_GAME_INITED); + this.emit(this.EVENT_GAME_INITED); - if (cb) cb(); + if (cb) cb(); + }); }, eventTargetOn: EventTarget.prototype.on, @@ -411,13 +424,14 @@ var game = { * @typescript * on(type: string, callback: T, target?: any, useCapture?: boolean): T */ - on (type, callback, target) { - // Make sure EVENT_ENGINE_INITED callbacks to be invoked - if (this._prepared && type === this.EVENT_ENGINE_INITED) { + on (type, callback, target, once) { + // Make sure EVENT_ENGINE_INITED and EVENT_GAME_INITED callbacks to be invoked + if ((this._prepared && type === this.EVENT_ENGINE_INITED) || + (!this._paused && type === this.EVENT_GAME_INITED)) { callback.call(target); } else { - this.eventTargetOn(type, callback, target); + this.eventTargetOn(type, callback, target, once); } }, /** @@ -439,8 +453,9 @@ var game = { * @param {Object} [target] - The target (this object) to invoke the callback, can be null */ once (type, callback, target) { - // Make sure EVENT_ENGINE_INITED callbacks to be invoked - if (this._prepared && type === this.EVENT_ENGINE_INITED) { + // Make sure EVENT_ENGINE_INITED and EVENT_GAME_INITED callbacks to be invoked + if ((this._prepared && type === this.EVENT_ENGINE_INITED) || + (!this._paused && type === this.EVENT_GAME_INITED)) { callback.call(target); } else { @@ -461,18 +476,9 @@ var game = { return; } - // Load game scripts - let jsList = this.config.jsList; - if (jsList && jsList.length > 0) { - var self = this; - cc.loader.load(jsList, function (err) { - if (err) throw new Error(JSON.stringify(err)); - self._prepareFinished(cb); - }); - } - else { + this._loadPreviewScript(() => { this._prepareFinished(cb); - } + }); }, /** @@ -522,6 +528,7 @@ var game = { } this._persistRootNodes[id] = node; node._persistNode = true; + cc.assetManager._releaseManager._addPersistNodeRef(node); } }, @@ -536,6 +543,7 @@ var game = { if (node === this._persistRootNodes[id]) { delete this._persistRootNodes[id]; node._persistNode = false; + cc.assetManager._releaseManager._removePersistNodeRef(node); } }, @@ -554,11 +562,11 @@ var game = { // @Time ticker section _setAnimFrame: function () { - this._lastTime = new Date(); + this._lastTime = performance.now(); var frameRate = game.config.frameRate; this._frameTime = 1000 / frameRate; - - if (CC_JSB) { + cc.director._maxParticleDeltaTime = this._frameTime / 1000 * 2; + if (CC_JSB || CC_RUNTIME) { jsb.setPreferredFramesPerSecond(frameRate); window.requestAnimFrame = window.requestAnimationFrame; window.cancelAnimFrame = window.cancelAnimationFrame; @@ -590,7 +598,7 @@ var game = { } }, _stTime: function(callback){ - var currTime = new Date().getTime(); + var currTime = performance.now(); var timeToCall = Math.max(0, game._frameTime - (currTime - game._lastTime)); var id = window.setTimeout(function() { callback(); }, timeToCall); @@ -602,21 +610,26 @@ var game = { }, //Run game. _runMainLoop: function () { + if (CC_EDITOR) { + return; + } + if (!this._prepared) return; + var self = this, callback, config = self.config, director = cc.director, skip = true, frameRate = config.frameRate; debug.setDisplayStats(config.showFPS); - callback = function () { + callback = function (now) { if (!self._paused) { self._intervalId = window.requestAnimFrame(callback); - if (!CC_JSB && frameRate === 30) { + if (!CC_JSB && !CC_RUNTIME && frameRate === 30) { if (skip = !skip) { return; } } - director.mainLoop(); + director.mainLoop(now); } }; @@ -641,10 +654,12 @@ var game = { if (typeof config.registerSystemEvent !== 'boolean') { config.registerSystemEvent = true; } - config.showFPS = !!config.showFPS; - - // Scene parser - this._sceneInfos = config.scenes || []; + if (renderMode === 1) { + config.showFPS = false; + } + else { + config.showFPS = !!config.showFPS; + } // Collide Map and Group List this.collisionMatrix = config.collisionMatrix || []; @@ -694,29 +709,14 @@ var game = { let el = this.config.id, width, height, - localCanvas, localContainer, - isWeChatGame = cc.sys.platform === cc.sys.WECHAT_GAME, - isQQPlay = cc.sys.platform === cc.sys.QQ_PLAY; + localCanvas, localContainer; - if (isWeChatGame || CC_JSB) { + if (CC_JSB || CC_RUNTIME) { this.container = localContainer = document.createElement("DIV"); this.frame = localContainer.parentNode === document.body ? document.documentElement : localContainer.parentNode; - if (cc.sys.browserType === cc.sys.BROWSER_TYPE_WECHAT_GAME_SUB) { - localCanvas = window.sharedCanvas || wx.getSharedCanvas(); - } - else if (CC_JSB) { - localCanvas = window.__canvas; - } - else { - localCanvas = canvas; - } + localCanvas = window.__canvas; this.canvas = localCanvas; } - else if (isQQPlay) { - this.container = cc.container = document.createElement("DIV"); - this.frame = document.documentElement; - this.canvas = localCanvas = canvas; - } else { var element = (el instanceof HTMLElement) ? el : (document.querySelector(el) || document.querySelector('#' + el)); @@ -768,9 +768,6 @@ var game = { 'antialias': cc.macro.ENABLE_WEBGL_ANTIALIAS, 'alpha': cc.macro.ENABLE_TRANSPARENT_CANVAS }; - if (isWeChatGame || isQQPlay) { - opts['preserveDrawingBuffer'] = true; - } renderer.initWebGL(localCanvas, opts); this._renderContext = renderer.device._gl; @@ -798,7 +795,7 @@ var game = { // register system events if (this.config.registerSystemEvent) - inputManager.registerSystemEvent(this.canvas); + cc.internal.inputManager.registerSystemEvent(this.canvas); if (typeof document.hidden !== 'undefined') { hiddenPropName = "hidden"; @@ -818,10 +815,11 @@ var game = { game.emit(game.EVENT_HIDE); } } - function onShown () { + // In order to adapt the most of platforms the onshow API. + function onShown (arg0, arg1, arg2, arg3, arg4) { if (hidden) { hidden = false; - game.emit(game.EVENT_SHOW); + game.emit(game.EVENT_SHOW, arg0, arg1, arg2, arg3, arg4); } } @@ -853,11 +851,6 @@ var game = { win.onfocus = onShown; } - if (CC_WECHATGAME && cc.sys.browserType !== cc.sys.BROWSER_TYPE_WECHAT_GAME_SUB) { - wx.onShow && wx.onShow(onShown); - wx.onHide && wx.onHide(onHidden); - } - if ("onpageshow" in window && "onpagehide" in window) { win.addEventListener("pagehide", onHidden); win.addEventListener("pageshow", onShown); @@ -879,6 +872,12 @@ EventTarget.call(game); cc.js.addon(game, EventTarget.prototype); /** + * @module cc + */ + +/** + * !#en This is a Game instance. + * !#zh 这是一个 Game 类的实例,包含游戏主体信息并负责驱动游戏的游戏对象。。 * @property game * @type Game */ diff --git a/cocos2d/core/CCNode.js b/cocos2d/core/CCNode.js index 59c9f5976d1..5db9f619b46 100644 --- a/cocos2d/core/CCNode.js +++ b/cocos2d/core/CCNode.js @@ -25,14 +25,14 @@ 'use strict'; +import { Mat4, Vec2, Vec3, Quat, Trs } from './value-types'; + const BaseNode = require('./utils/base-node'); const PrefabHelper = require('./utils/prefab-helper'); -const mathPools = require('./utils/math-pools'); -const math = require('./renderer/render-engine').math; +const nodeMemPool = require('./utils/trans-pool').NodeMemPool; const AffineTrans = require('./utils/affine-transform'); const eventManager = require('./event-manager'); const macro = require('./platform/CCMacro'); -const misc = require('./utils/misc'); const js = require('./platform/js'); const Event = require('./event/event'); const EventTarget = require('./event/event-target'); @@ -46,12 +46,54 @@ const ONE_DEGREE = Math.PI / 180; var ActionManagerExist = !!cc.ActionManager; var emptyFunc = function () {}; -var _vec2a = cc.v2(); -var _vec2b = cc.v2(); -var _mat4_temp = math.mat4.create(); -var _vec3_temp = math.vec3.create(); -var _quat_temp = math.quat.create(); -var _globalOrderOfArrival = 1; + +// getWorldPosition temp var +var _gwpVec3 = new Vec3(); +var _gwpQuat = new Quat(); + +// _invTransformPoint temp var +var _tpVec3a = new Vec3(); +var _tpVec3b = new Vec3(); +var _tpQuata = new Quat(); +var _tpQuatb = new Quat(); + +// setWorldPosition temp var +var _swpVec3 = new Vec3(); + +// getWorldScale temp var +var _gwsVec3 = new Vec3(); + +// setWorldScale temp var +var _swsVec3 = new Vec3(); + +// getWorldRT temp var +var _gwrtVec3a = new Vec3(); +var _gwrtVec3b = new Vec3(); +var _gwrtQuata = new Quat(); +var _gwrtQuatb = new Quat(); + +// lookAt temp var +var _laVec3 = new Vec3(); +var _laQuat = new Quat(); + +//up、right、forward temp var +var _urfVec3 = new Vec3(); +var _urfQuat = new Quat(); + +// _hitTest temp var +var _htVec3a = new Vec3(); +var _htVec3b = new Vec3(); + +// getWorldRotation temp var +var _gwrQuat = new Quat(); + +// setWorldRotation temp var +var _swrQuat = new Quat(); + +var _quata = new Quat(); +var _mat4_temp = cc.mat4(); +var _vec3_temp = new Vec3(); + var _cachedArray = new Array(16); _cachedArray.length = 0; @@ -60,11 +102,12 @@ const SCALE_ON = 1 << 1; const ROTATION_ON = 1 << 2; const SIZE_ON = 1 << 3; const ANCHOR_ON = 1 << 4; +const COLOR_ON = 1 << 5; let BuiltinGroupIndex = cc.Enum({ DEBUG: 31 -}) +}); /** * !#en Node's local dirty properties flag @@ -104,12 +147,99 @@ var LocalDirtyFlag = cc.Enum({ */ SKEW: 1 << 3, /** - * !#en Flag for position or rotation dirty - * !#zh 旋转或位置 dirty 的标记位 - * @property {Number} RT + * !#en Flag for rotation, scale or position dirty + * !#zh 旋转,缩放,或位置 dirty 的标记位 + * @property {Number} TRS + * @static + */ + TRS: 1 << 0 | 1 << 1 | 1 << 2, + /** + * !#en Flag for rotation or scale dirty + * !#zh 旋转或缩放 dirty 的标记位 + * @property {Number} RS + * @static + */ + RS: 1 << 1 | 1 << 2, + /** + * !#en Flag for rotation, scale, position, skew dirty + * !#zh 旋转,缩放,位置,或斜角 dirty 的标记位 + * @property {Number} TRS + * @static + */ + TRSS: 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3, + + /** + * !#en Flag for physics position dirty + * !#zh 物理位置 dirty 的标记位 + * @property {Number} PHYSICS_POSITION + * @static + */ + PHYSICS_POSITION: 1 << 4, + + /** + * !#en Flag for physics scale dirty + * !#zh 物理缩放 dirty 的标记位 + * @property {Number} PHYSICS_SCALE + * @static + */ + PHYSICS_SCALE: 1 << 5, + + /** + * !#en Flag for physics rotation dirty + * !#zh 物理旋转 dirty 的标记位 + * @property {Number} PHYSICS_ROTATION + * @static + */ + PHYSICS_ROTATION: 1 << 6, + + /** + * !#en Flag for physics trs dirty + * !#zh 物理位置旋转缩放 dirty 的标记位 + * @property {Number} PHYSICS_TRS * @static */ - RT: 1 << 0 | 1 << 1 | 1 << 2, + PHYSICS_TRS: 1 << 4 | 1 << 5 | 1 << 6, + + /** + * !#en Flag for physics rs dirty + * !#zh 物理旋转缩放 dirty 的标记位 + * @property {Number} PHYSICS_RS + * @static + */ + PHYSICS_RS: 1 << 5 | 1 << 6, + + /** + * !#en Flag for node and physics position dirty + * !#zh 所有位置 dirty 的标记位 + * @property {Number} ALL_POSITION + * @static + */ + ALL_POSITION: 1 << 0 | 1 << 4, + + /** + * !#en Flag for node and physics scale dirty + * !#zh 所有缩放 dirty 的标记位 + * @property {Number} ALL_SCALE + * @static + */ + ALL_SCALE: 1 << 1 | 1 << 5, + + /** + * !#en Flag for node and physics rotation dirty + * !#zh 所有旋转 dirty 的标记位 + * @property {Number} ALL_ROTATION + * @static + */ + ALL_ROTATION: 1 << 2 | 1 << 6, + + /** + * !#en Flag for node and physics trs dirty + * !#zh 所有trs dirty 的标记位 + * @property {Number} ALL_TRS + * @static + */ + ALL_TRS: 1 << 0 | 1 << 1 | 1 << 2 | 1 << 4 | 1 << 5 | 1 << 6, + /** * !#en Flag for all dirty properties * !#zh 覆盖所有 dirty 状态的标记位 @@ -126,6 +256,7 @@ var LocalDirtyFlag = cc.Enum({ * @static * @namespace Node */ +// Why EventType defined as class, because the first parameter of Node.on method needs set as 'string' type. var EventType = cc.Enum({ /** * !#en The event type for touch start event, you can use its value directly: 'touchstart' @@ -249,6 +380,16 @@ var EventType = cc.Enum({ * @static */ ANCHOR_CHANGED: 'anchor-changed', + /** + * !#en The event type for color change events. + * Performance note, this event will be triggered every time corresponding properties being changed, + * if the event callback have heavy logic it may have great performance impact, try to avoid such scenario. + * !#zh 当节点颜色改变时触发的事件。 + * 性能警告:这个事件会在每次对应的属性被修改时触发,如果事件回调损耗较高,有可能对性能有很大的负面影响,请尽量避免这种情况。 + * @property {String} COLOR_CHANGED + * @static + */ + COLOR_CHANGED: 'color-changed', /** * !#en The event type for new child added events. * !#zh 当新的子节点被添加时触发的事件。 @@ -277,6 +418,13 @@ var EventType = cc.Enum({ * @static */ GROUP_CHANGED: 'group-changed', + /** + * !#en The event type for node's sibling order changed. + * !#zh 当节点在兄弟节点中的顺序发生变化时触发的事件。 + * @property {String} SIBLING_ORDER_CHANGED + * @static + */ + SIBLING_ORDER_CHANGED: 'sibling-order-changed', }); var _touchEvents = [ @@ -294,6 +442,19 @@ var _mouseEvents = [ EventType.MOUSE_WHEEL, ]; +var _skewNeedWarn = true; +var _skewWarn = function (value, node) { + if (value !== 0) { + var nodePath = ""; + if (CC_EDITOR) { + var NodeUtils = Editor.require('scene://utils/node'); + nodePath = `Node: ${NodeUtils.getNodePath(node)}.` + } + _skewNeedWarn && cc.warn("`cc.Node.skewX/Y` is deprecated since v2.2.1, please use 3D node instead.", nodePath); + !CC_EDITOR && (_skewNeedWarn = false); + } +} + var _currentHovered = null; var _touchStartHandler = function (touch, event) { @@ -408,34 +569,42 @@ var _mouseWheelHandler = function (event) { } }; -function _searchMaskInParent (node) { - var Mask = cc.Mask; - if (Mask) { - var index = 0; +function _searchComponentsInParent (node, comp) { + if (comp) { + let index = 0; + let list = null; for (var curr = node; curr && cc.Node.isNode(curr); curr = curr._parent, ++index) { - if (curr.getComponent(Mask)) { - return { + if (curr.getComponent(comp)) { + let next = { index: index, - node: curr + node: curr, }; + + if (list) { + list.push(next); + } else { + list = [next]; + } } } + + return list; } + return null; } function _checkListeners (node, events) { if (!(node._objFlags & Destroying)) { - var i = 0; if (node._bubblingListeners) { - for (; i < events.length; ++i) { + for (let i = 0, l = events.length; i < l; ++i) { if (node._bubblingListeners.hasEventListener(events[i])) { return true; } } } if (node._capturingListeners) { - for (; i < events.length; ++i) { + for (let i = 0, l = events.length; i < l; ++i) { if (node._capturingListeners.hasEventListener(events[i])) { return true; } @@ -503,6 +672,172 @@ function _doDispatchEvent (owner, event) { _cachedArray.length = 0; } +// traversal the node tree, child cullingMask must keep the same with the parent. +function _getActualGroupIndex (node) { + let groupIndex = node.groupIndex; + if (groupIndex === 0 && node.parent) { + groupIndex = _getActualGroupIndex(node.parent); + } + return groupIndex; +} + +function _updateCullingMask (node) { + let index = _getActualGroupIndex(node); + node._cullingMask = 1 << index; + if (CC_JSB && CC_NATIVERENDERER) { + node._proxy && node._proxy.updateCullingMask(); + } + for (let i = 0; i < node._children.length; i++) { + _updateCullingMask(node._children[i]); + } +} + +// 2D/3D matrix functions +function updateLocalMatrix3D () { + if (this._localMatDirty & LocalDirtyFlag.TRSS) { + // Update transform + let t = this._matrix; + let tm = t.m; + Trs.toMat4(t, this._trs); + + // skew + if (this._skewX || this._skewY) { + let a = tm[0], b = tm[1], c = tm[4], d = tm[5]; + let skx = Math.tan(this._skewX * ONE_DEGREE); + let sky = Math.tan(this._skewY * ONE_DEGREE); + if (skx === Infinity) + skx = 99999999; + if (sky === Infinity) + sky = 99999999; + tm[0] = a + c * sky; + tm[1] = b + d * sky; + tm[4] = c + a * skx; + tm[5] = d + b * skx; + } + this._localMatDirty &= ~LocalDirtyFlag.TRSS; + // Register dirty status of world matrix so that it can be recalculated + this._worldMatDirty = true; + } +} + +function updateLocalMatrix2D () { + let dirtyFlag = this._localMatDirty; + if (!(dirtyFlag & LocalDirtyFlag.TRSS)) return; + + // Update transform + let t = this._matrix; + let tm = t.m; + let trs = this._trs; + + if (dirtyFlag & (LocalDirtyFlag.RS | LocalDirtyFlag.SKEW)) { + let rotation = -this._eulerAngles.z; + let hasSkew = this._skewX || this._skewY; + let sx = trs[7], sy = trs[8]; + + if (rotation || hasSkew) { + let a = 1, b = 0, c = 0, d = 1; + // rotation + if (rotation) { + let rotationRadians = rotation * ONE_DEGREE; + c = Math.sin(rotationRadians); + d = Math.cos(rotationRadians); + a = d; + b = -c; + } + // scale + tm[0] = a *= sx; + tm[1] = b *= sx; + tm[4] = c *= sy; + tm[5] = d *= sy; + // skew + if (hasSkew) { + let a = tm[0], b = tm[1], c = tm[4], d = tm[5]; + let skx = Math.tan(this._skewX * ONE_DEGREE); + let sky = Math.tan(this._skewY * ONE_DEGREE); + if (skx === Infinity) + skx = 99999999; + if (sky === Infinity) + sky = 99999999; + tm[0] = a + c * sky; + tm[1] = b + d * sky; + tm[4] = c + a * skx; + tm[5] = d + b * skx; + } + } + else { + tm[0] = sx; + tm[1] = 0; + tm[4] = 0; + tm[5] = sy; + } + } + + // position + tm[12] = trs[0]; + tm[13] = trs[1]; + + this._localMatDirty &= ~LocalDirtyFlag.TRSS; + // Register dirty status of world matrix so that it can be recalculated + this._worldMatDirty = true; +} + +function calculWorldMatrix3D () { + // Avoid as much function call as possible + if (this._localMatDirty & LocalDirtyFlag.TRSS) { + this._updateLocalMatrix(); + } + + if (this._parent) { + let parentMat = this._parent._worldMatrix; + Mat4.mul(this._worldMatrix, parentMat, this._matrix); + } + else { + Mat4.copy(this._worldMatrix, this._matrix); + } + this._worldMatDirty = false; +} + +function calculWorldMatrix2D () { + // Avoid as much function call as possible + if (this._localMatDirty & LocalDirtyFlag.TRSS) { + this._updateLocalMatrix(); + } + + // Assume parent world matrix is correct + let parent = this._parent; + if (parent) { + this._mulMat(this._worldMatrix, parent._worldMatrix, this._matrix); + } + else { + Mat4.copy(this._worldMatrix, this._matrix); + } + this._worldMatDirty = false; +} + +function mulMat2D (out, a, b) { + let am = a.m, bm = b.m, outm = out.m; + let aa=am[0], ab=am[1], ac=am[4], ad=am[5], atx=am[12], aty=am[13]; + let ba=bm[0], bb=bm[1], bc=bm[4], bd=bm[5], btx=bm[12], bty=bm[13]; + if (ab !== 0 || ac !== 0) { + outm[0] = ba * aa + bb * ac; + outm[1] = ba * ab + bb * ad; + outm[4] = bc * aa + bd * ac; + outm[5] = bc * ab + bd * ad; + outm[12] = aa * btx + ac * bty + atx; + outm[13] = ab * btx + ad * bty + aty; + } + else { + outm[0] = ba * aa; + outm[1] = bb * ad; + outm[4] = bc * aa; + outm[5] = bd * ad; + outm[12] = aa * btx + atx; + outm[13] = ad * bty + aty; + } +} + +const mulMat3D = Mat4.mul; + /** * !#en * Class of all entities in Cocos Creator scenes.
@@ -513,7 +848,7 @@ function _doDispatchEvent (owner, event) { * @class Node * @extends _BaseNode */ -var Node = cc.Class({ +let NodeDefines = { name: 'cc.Node', extends: BaseNode, @@ -523,31 +858,24 @@ var Node = cc.Class({ _color: cc.Color.WHITE, _contentSize: cc.Size, _anchorPoint: cc.v2(0.5, 0.5), - _position: cc.Vec3, - _scaleX: { - default: undefined, - type: cc.Float - }, - _scaleY: { - default: undefined, - type: cc.Float - }, - _scale: cc.Vec3, - _rotationX: 0.0, - _rotationY: 0.0, - _quat: cc.Quat, + _position: undefined, + _scale: undefined, + _trs: null, + _eulerAngles: cc.Vec3, _skewX: 0.0, _skewY: 0.0, + _zIndex: { + default: undefined, + type: cc.Integer + }, _localZOrder: { default: 0, serializable: false }, - _zIndex: 0, _is3DNode: false, // internal properties - /** * !#en * Group index of node.
@@ -559,9 +887,19 @@ var Node = cc.Class({ * @type {Integer} * @default 0 */ - groupIndex: { + _groupIndex: { default: 0, - type: cc.Integer + formerlySerializedAs: 'groupIndex' + }, + groupIndex: { + get () { + return this._groupIndex; + }, + set (value) { + this._groupIndex = value; + _updateCullingMask(this); + this.emit(EventType.GROUP_CHANGED, this); + } }, /** @@ -580,8 +918,8 @@ var Node = cc.Class({ }, set (value) { + // update the groupIndex this.groupIndex = cc.game.groupList.indexOf(value); - this.emit(EventType.GROUP_CHANGED, this); } }, @@ -590,7 +928,7 @@ var Node = cc.Class({ /** * !#en The position (x, y) of the node in its parent's coordinates. * !#zh 节点在父节点坐标系中的位置(x, y)。 - * @property {Vec2} position + * @property {Vec3} position * @example * cc.log("Node Position: " + node.position); */ @@ -606,25 +944,25 @@ var Node = cc.Class({ */ x: { get () { - return this._position.x; + return this._trs[0]; }, set (value) { - var localPosition = this._position; - if (value !== localPosition.x) { + let trs = this._trs; + if (value !== trs[0]) { if (!CC_EDITOR || isFinite(value)) { + let oldValue; if (CC_EDITOR) { - var oldValue = localPosition.x; + oldValue = trs[0]; } - localPosition.x = value; - this.setLocalDirty(LocalDirtyFlag.POSITION); - this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM; - + trs[0] = value; + this.setLocalDirty(LocalDirtyFlag.ALL_POSITION); + // fast check event if (this._eventMask & POSITION_ON) { // send event if (CC_EDITOR) { - this.emit(EventType.POSITION_CHANGED, new cc.Vec2(oldValue, localPosition.y)); + this.emit(EventType.POSITION_CHANGED, new cc.Vec3(oldValue, trs[1], trs[2])); } else { this.emit(EventType.POSITION_CHANGED); @@ -649,25 +987,25 @@ var Node = cc.Class({ */ y: { get () { - return this._position.y; + return this._trs[1]; }, set (value) { - var localPosition = this._position; - if (value !== localPosition.y) { + let trs = this._trs; + if (value !== trs[1]) { if (!CC_EDITOR || isFinite(value)) { + let oldValue; if (CC_EDITOR) { - var oldValue = localPosition.y; + oldValue = trs[1]; } - localPosition.y = value; - this.setLocalDirty(LocalDirtyFlag.POSITION); - this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM; + trs[1] = value; + this.setLocalDirty(LocalDirtyFlag.ALL_POSITION); // fast check event if (this._eventMask & POSITION_ON) { // send event if (CC_EDITOR) { - this.emit(EventType.POSITION_CHANGED, new cc.Vec2(localPosition.x, oldValue)); + this.emit(EventType.POSITION_CHANGED, new cc.Vec3(trs[0], oldValue, trs[2])); } else { this.emit(EventType.POSITION_CHANGED); @@ -680,28 +1018,43 @@ var Node = cc.Class({ } }, }, - + + /** + * !#en z axis position of node. + * !#zh 节点 Z 轴坐标。 + * @property z + * @type {Number} + */ z: { get () { - return this._position.z; + return this._trs[2]; }, set (value) { - var localPosition = this._position; - if (value !== localPosition.z) { + let trs = this._trs; + if (value !== trs[2]) { if (!CC_EDITOR || isFinite(value)) { - localPosition.z = value; - this.setLocalDirty(LocalDirtyFlag.POSITION); - this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM; + let oldValue; + if (CC_EDITOR) { + oldValue = trs[2]; + } + trs[2] = value; + this.setLocalDirty(LocalDirtyFlag.ALL_POSITION); + !CC_NATIVERENDERER && (this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM); // fast check event if (this._eventMask & POSITION_ON) { - this.emit(EventType.POSITION_CHANGED); + if (CC_EDITOR) { + this.emit(EventType.POSITION_CHANGED, new cc.Vec3(trs[0], trs[1], oldValue)); + } + else { + this.emit(EventType.POSITION_CHANGED); + } } } else { cc.error(ERR_INVALID_NUMBER, 'new z'); } } - }, + } }, /** @@ -709,56 +1062,92 @@ var Node = cc.Class({ * !#zh 该节点旋转角度。 * @property rotation * @type {Number} + * @deprecated since v2.1 * @example * node.rotation = 90; * cc.log("Node Rotation: " + node.rotation); */ + rotation: { + get () { + if (CC_DEBUG) { + cc.warn("`cc.Node.rotation` is deprecated since v2.1.0, please use `-angle` instead. (`this.node.rotation` -> `-this.node.angle`)"); + } + return -this.angle; + }, + set (value) { + if (CC_DEBUG) { + cc.warn("`cc.Node.rotation` is deprecated since v2.1.0, please set `-angle` instead. (`this.node.rotation = x` -> `this.node.angle = -x`)"); + } + this.angle = -value; + } + }, /** - * !#en The rotation as Euler angles in degrees, used in 2.5D project. - * !#zh 该节点的欧拉角度,用于 2.5D 项目。 - * @property eulerAngles - * @type {Vec3} + * !#en + * Angle of node, the positive value is anti-clockwise direction. + * !#zh + * 该节点的旋转角度,正值为逆时针方向。 + * @property angle + * @type {Number} */ - eulerAngles: { + angle: { get () { - return cc.v3(this._rotationX, this._rotationY, this._rotationZ); + return this._eulerAngles.z; }, - set (v) { - this._rotationX = v.x; - this._rotationY = v.y; - this._rotationZ = v.z; - math.quat.fromEuler(this._quat, v.x, v.y, v.z); - this.setLocalDirty(LocalDirtyFlag.ROTATION); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + set (value) { + Vec3.set(this._eulerAngles, 0, 0, value); + Trs.fromAngleZ(this._trs, value); + this.setLocalDirty(LocalDirtyFlag.ALL_ROTATION); + + if (this._eventMask & ROTATION_ON) { + this.emit(EventType.ROTATION_CHANGED); + } } }, + /** + * !#en The rotation as Euler angles in degrees, used in 3D node. + * !#zh 该节点的欧拉角度,用于 3D 节点。 + * @property eulerAngles + * @type {Vec3} + * @example + * node.is3DNode = true; + * node.eulerAngles = cc.v3(45, 45, 45); + * cc.log("Node eulerAngles (X, Y, Z): " + node.eulerAngles.toString()); + */ + /** * !#en Rotation on x axis. * !#zh 该节点 X 轴旋转角度。 * @property rotationX * @type {Number} + * @deprecated since v2.1 * @example - * node.rotationX = 45; - * cc.log("Node Rotation X: " + node.rotationX); + * node.is3DNode = true; + * node.eulerAngles = cc.v3(45, 0, 0); + * cc.log("Node eulerAngles X: " + node.eulerAngles.x); */ rotationX: { get () { - return this._rotationX; + if (CC_DEBUG) { + cc.warn("`cc.Node.rotationX` is deprecated since v2.1.0, please use `eulerAngles.x` instead. (`this.node.rotationX` -> `this.node.eulerAngles.x`)"); + } + return this._eulerAngles.x; }, set (value) { - if (this._rotationX !== value) { - this._rotationX = value; + if (CC_DEBUG) { + cc.warn("`cc.Node.rotationX` is deprecated since v2.1.0, please set `eulerAngles` instead. (`this.node.rotationX = x` -> `this.node.is3DNode = true; this.node.eulerAngles = cc.v3(x, 0, 0)`"); + } + if (this._eulerAngles.x !== value) { + this._eulerAngles.x = value; // Update quaternion from rotation - if (this._rotationX === this._rotationY) { - math.quat.fromEuler(this._quat, 0, 0, -value); + if (this._eulerAngles.x === this._eulerAngles.y) { + Trs.fromAngleZ(this._trs, -value); } else { - math.quat.fromEuler(this._quat, value, this._rotationY, 0); + Trs.fromEulerNumber(this._trs, value, this._eulerAngles.y, 0); } - this.setLocalDirty(LocalDirtyFlag.ROTATION); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + this.setLocalDirty(LocalDirtyFlag.ALL_ROTATION); if (this._eventMask & ROTATION_ON) { this.emit(EventType.ROTATION_CHANGED); @@ -772,26 +1161,33 @@ var Node = cc.Class({ * !#zh 该节点 Y 轴旋转角度。 * @property rotationY * @type {Number} + * @deprecated since v2.1 * @example - * node.rotationY = 45; - * cc.log("Node Rotation Y: " + node.rotationY); + * node.is3DNode = true; + * node.eulerAngles = cc.v3(0, 45, 0); + * cc.log("Node eulerAngles Y: " + node.eulerAngles.y); */ rotationY: { get () { - return this._rotationY; + if (CC_DEBUG) { + cc.warn("`cc.Node.rotationY` is deprecated since v2.1.0, please use `eulerAngles.y` instead. (`this.node.rotationY` -> `this.node.eulerAngles.y`)"); + } + return this._eulerAngles.y; }, set (value) { - if (this._rotationY !== value) { - this._rotationY = value; + if (CC_DEBUG) { + cc.warn("`cc.Node.rotationY` is deprecated since v2.1.0, please set `eulerAngles` instead. (`this.node.rotationY = y` -> `this.node.is3DNode = true; this.node.eulerAngles = cc.v3(0, y, 0)`"); + } + if (this._eulerAngles.y !== value) { + this._eulerAngles.y = value; // Update quaternion from rotation - if (this._rotationX === this._rotationY) { - math.quat.fromEuler(this._quat, 0, 0, -value); + if (this._eulerAngles.x === this._eulerAngles.y) { + Trs.fromAngleZ(this._trs, -value); } else { - math.quat.fromEuler(this._quat, this._rotationX, value, 0); + Trs.fromEulerNumber(this._trs, this._eulerAngles.x, value, 0); } - this.setLocalDirty(LocalDirtyFlag.ROTATION); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + this.setLocalDirty(LocalDirtyFlag.ALL_ROTATION); if (this._eventMask & ROTATION_ON) { this.emit(EventType.ROTATION_CHANGED); @@ -800,6 +1196,40 @@ var Node = cc.Class({ }, }, + eulerAngles: { + get () { + if (CC_EDITOR) { + return this._eulerAngles; + } + else { + return Trs.toEuler(this._eulerAngles, this._trs); + } + }, set (v) { + if (CC_EDITOR) { + this._eulerAngles.set(v); + } + + Trs.fromEuler(this._trs, v); + this.setLocalDirty(LocalDirtyFlag.ALL_ROTATION); + !CC_NATIVERENDERER && (this._renderFlag |= RenderFlow.FLAG_TRANSFORM); + + if (this._eventMask & ROTATION_ON) { + this.emit(EventType.ROTATION_CHANGED); + } + } + }, + + // This property is used for Mesh Skeleton Animation + // Should be removed when node.rotation upgrade to quaternion value + quat: { + get () { + let trs = this._trs; + return new Quat(trs[3], trs[4], trs[5], trs[6]); + }, set (v) { + this.setRotation(v); + } + }, + /** * !#en The local scale relative to the parent. * !#zh 节点相对父节点的缩放。 @@ -808,6 +1238,14 @@ var Node = cc.Class({ * @example * node.scale = 1; */ + scale: { + get () { + return this._trs[7]; + }, + set (v) { + this.setScale(v); + } + }, /** * !#en Scale on x axis. @@ -820,13 +1258,12 @@ var Node = cc.Class({ */ scaleX: { get () { - return this._scale.x; + return this._trs[7]; }, set (value) { - if (this._scale.x !== value) { - this._scale.x = value; - this.setLocalDirty(LocalDirtyFlag.SCALE); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + if (this._trs[7] !== value) { + this._trs[7] = value; + this.setLocalDirty(LocalDirtyFlag.ALL_SCALE); if (this._eventMask & SCALE_ON) { this.emit(EventType.SCALE_CHANGED); @@ -846,13 +1283,12 @@ var Node = cc.Class({ */ scaleY: { get () { - return this._scale.y; + return this._trs[8]; }, set (value) { - if (this._scale.y !== value) { - this._scale.y = value; - this.setLocalDirty(LocalDirtyFlag.SCALE); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + if (this._trs[8] !== value) { + this._trs[8] = value; + this.setLocalDirty(LocalDirtyFlag.ALL_SCALE); if (this._eventMask & SCALE_ON) { this.emit(EventType.SCALE_CHANGED); @@ -861,6 +1297,29 @@ var Node = cc.Class({ }, }, + /** + * !#en Scale on z axis. + * !#zh 节点 Z 轴缩放。 + * @property scaleZ + * @type {Number} + */ + scaleZ: { + get () { + return this._trs[9]; + }, + set (value) { + if (this._trs[9] !== value) { + this._trs[9] = value; + this.setLocalDirty(LocalDirtyFlag.ALL_SCALE); + !CC_NATIVERENDERER && (this._renderFlag |= RenderFlow.FLAG_TRANSFORM); + + if (this._eventMask & SCALE_ON) { + this.emit(EventType.SCALE_CHANGED); + } + } + } + }, + /** * !#en Skew x * !#zh 该节点 X 轴倾斜角度。 @@ -869,15 +1328,20 @@ var Node = cc.Class({ * @example * node.skewX = 0; * cc.log("Node SkewX: " + node.skewX); + * @deprecated since v2.2.1 */ skewX: { get () { return this._skewX; }, set (value) { + _skewWarn(value, this); + this._skewX = value; this.setLocalDirty(LocalDirtyFlag.SKEW); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.updateSkew(); + } } }, @@ -889,15 +1353,20 @@ var Node = cc.Class({ * @example * node.skewY = 0; * cc.log("Node SkewY: " + node.skewY); + * @deprecated since v2.2.1 */ skewY: { get () { return this._skewY; }, set (value) { + _skewWarn(value, this); + this._skewY = value; this.setLocalDirty(LocalDirtyFlag.SKEW); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.updateSkew(); + } } }, @@ -914,9 +1383,13 @@ var Node = cc.Class({ return this._opacity; }, set (value) { + value = cc.misc.clampf(value, 0, 255); if (this._opacity !== value) { this._opacity = value; - this._renderFlag |= RenderFlow.FLAG_OPACITY | RenderFlow.FLAG_COLOR; + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.updateOpacity(); + } + this._renderFlag |= RenderFlow.FLAG_OPACITY_COLOR; } }, range: [0, 255] @@ -940,9 +1413,11 @@ var Node = cc.Class({ if (CC_DEV && value.a !== 255) { cc.warnID(1626); } - - if (this._renderComponent) { - this._renderFlag |= RenderFlow.FLAG_COLOR; + + this._renderFlag |= RenderFlow.FLAG_COLOR; + + if (this._eventMask & COLOR_ON) { + this.emit(EventType.COLOR_CHANGED, value); } } }, @@ -1073,7 +1548,7 @@ var Node = cc.Class({ */ zIndex: { get () { - return this._zIndex; + return this._localZOrder >> 16; }, set (value) { if (value > macro.MAX_ZINDEX) { @@ -1085,16 +1560,73 @@ var Node = cc.Class({ value = macro.MIN_ZINDEX; } - if (this._zIndex !== value) { - this._zIndex = value; + if (this.zIndex !== value) { this._localZOrder = (this._localZOrder & 0x0000ffff) | (value << 16); + this.emit(EventType.SIBLING_ORDER_CHANGED); - if (this._parent) { - this._parent._delaySort(); - } + this._onSiblingIndexChanged(); } } }, + + /** + * !#en + * Switch 2D/3D node. The 2D nodes will run faster. + * !#zh + * 切换 2D/3D 节点,2D 节点会有更高的运行效率 + * @property {Boolean} is3DNode + * @default false + */ + is3DNode: { + get () { + return this._is3DNode; + }, set (v) { + this._is3DNode = v; + this._update3DFunction(); + } + }, + + /** + * !#en Returns a normalized vector representing the up direction (Y axis) of the node in world space. + * !#zh 获取节点正上方(y 轴)面对的方向,返回值为世界坐标系下的归一化向量 + * + * @property up + * @type {Vec3} + */ + up: { + get () { + var _up = Vec3.transformQuat(_urfVec3, Vec3.UP, this.getWorldRotation(_urfQuat)); + return _up.clone(); + } + }, + + /** + * !#en Returns a normalized vector representing the right direction (X axis) of the node in world space. + * !#zh 获取节点正右方(x 轴)面对的方向,返回值为世界坐标系下的归一化向量 + * + * @property right + * @type {Vec3} + */ + right: { + get () { + var _right = Vec3.transformQuat(_urfVec3, Vec3.RIGHT, this.getWorldRotation(_urfQuat)); + return _right.clone(); + } + }, + + /** + * !#en Returns a normalized vector representing the forward direction (Z axis) of the node in world space. + * !#zh 获取节点正前方(z 轴)面对的方向,返回值为世界坐标系下的归一化向量 + * + * @property forward + * @type {Vec3} + */ + forward: { + get () { + var _forward = Vec3.transformQuat(_urfVec3, Vec3.FORWARD, this.getWorldRotation(_urfQuat)); + return _forward.clone(); + } + }, }, /** @@ -1103,7 +1635,7 @@ var Node = cc.Class({ */ ctor () { this._reorderChildDirty = false; - + // cache component this._widget = null; // fast render component access @@ -1116,20 +1648,19 @@ var Node = cc.Class({ // Mouse event listener this._mouseListener = null; - // default scale - this._scale.x = 1; - this._scale.y = 1; - this._scale.z = 1; - - this._matrix = mathPools.mat4.get(); - this._worldMatrix = mathPools.mat4.get(); - this._localMatDirty = LocalDirtyFlag.ALL; - this._worldMatDirty = true; + this._initDataFromPool(); this._eventMask = 0; - this._cullingMask = 1 << this.groupIndex; + this._cullingMask = 1; + this._childArrivalOrder = 1; - this._rotationZ = 0; + // Proxy + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy = new renderer.NodeProxy(this._spaceInfo.unitID, this._spaceInfo.index, this._id, this._name); + this._proxy.init(this); + } + // should reset _renderFlag for both web and native + this._renderFlag = RenderFlow.FLAG_TRANSFORM | RenderFlow.FLAG_OPACITY_COLOR; }, statics: { @@ -1139,23 +1670,16 @@ var Node = cc.Class({ isNode (obj) { return obj instanceof Node && (obj.constructor === Node || !(obj instanceof cc.Scene)); }, - BuiltinGroupIndex }, // OVERRIDES - _onSiblingIndexChanged (index) { + _onSiblingIndexChanged () { // update rendering scene graph, sort them by arrivalOrder - var parent = this._parent; - var siblings = parent._children; - var i = 0, len = siblings.length, sibling; - for (; i < len; i++) { - sibling = siblings[i]; - sibling._updateOrderOfArrival(); - eventManager._setDirtyForNode(sibling); + if (this._parent) { + this._parent._delaySort(); } - parent._delaySort(); }, _onPreDestroy () { @@ -1171,6 +1695,9 @@ var Node = cc.Class({ _currentHovered = null; } + this._bubblingListeners && this._bubblingListeners.clear(); + this._capturingListeners && this._capturingListeners.clear(); + // Remove all event listeners if necessary if (this._touchListener || this._mouseListener) { eventManager.removeListeners(this); @@ -1186,10 +1713,12 @@ var Node = cc.Class({ } } - // Recycle math objects - mathPools.mat4.put(this._matrix); - mathPools.mat4.put(this._worldMatrix); - this._matrix = this._worldMatrix = null; + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.destroy(); + this._proxy = null; + } + + this._backDataIntoPool(); if (this._reorderChildDirty) { cc.director.__fastOff(cc.Director.EVENT_AFTER_UPDATE, this.sortAllChildren, this); @@ -1212,17 +1741,9 @@ var Node = cc.Class({ // ActionManager & EventManager actionManager && actionManager.resumeTarget(this); eventManager.resumeTarget(this); - if (this._touchListener) { - var mask = this._touchListener.mask = _searchMaskInParent(this); - if (this._mouseListener) { - this._mouseListener.mask = mask; - } - } - else if (this._mouseListener) { - this._mouseListener.mask = _searchMaskInParent(this); - } - } - else { + // Search Mask in parent + this._checkListenerMask(); + } else { // deactivate actionManager && actionManager.pauseTarget(this); eventManager.pauseTarget(this); @@ -1231,6 +1752,8 @@ var Node = cc.Class({ _onHierarchyChanged (oldParent) { this._updateOrderOfArrival(); + // Fixed a bug where children and parent node groups were forced to synchronize, instead of only synchronizing `_cullingMask` value + _updateCullingMask(this); if (this._parent) { this._parent._delaySort(); } @@ -1239,125 +1762,195 @@ var Node = cc.Class({ if (cc._widgetManager) { cc._widgetManager._nodesOrderDirty = true; } + + if (oldParent && this._activeInHierarchy) { + //TODO: It may be necessary to update the listener mask of all child nodes. + this._checkListenerMask(); + } + + // Node proxy + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.updateParent(); + } }, // INTERNAL - _syncEulerAngles () { - let quat = this._quat; - let rotx = quat.getRoll(); - let roty = quat.getPitch(); - if (rotx === 0 && roty === 0) { - this._rotationX = this._rotationY = -quat.getYaw(); + _update3DFunction () { + if (this._is3DNode) { + this._updateLocalMatrix = updateLocalMatrix3D; + this._calculWorldMatrix = calculWorldMatrix3D; + this._mulMat = mulMat3D; } else { - this._rotationX = rotx; - this._rotationY = roty; + this._updateLocalMatrix = updateLocalMatrix2D; + this._calculWorldMatrix = calculWorldMatrix2D; + this._mulMat = mulMat2D; } - }, + if (this._renderComponent && this._renderComponent._on3DNodeChanged) { + this._renderComponent._on3DNodeChanged(); + } + this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + this._localMatDirty = LocalDirtyFlag.ALL; - _upgrade_1x_to_2x () { - // Upgrade scaleX, scaleY from v1.x - // TODO: remove in future version, 3.0 ? - if (this._scaleX !== undefined) { - this._scale.x = this._scaleX; - this._scaleX = undefined; + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.update3DNode(); } - if (this._scaleY !== undefined) { - this._scale.y = this._scaleY; - this._scaleY = undefined; + }, + + _initDataFromPool () { + if (!this._spaceInfo) { + if (CC_EDITOR || CC_TEST) { + this._spaceInfo = { + trs: new Float64Array(10), + localMat: new Float64Array(16), + worldMat: new Float64Array(16), + }; + } else { + this._spaceInfo = nodeMemPool.pop(); + } } - if (this._localZOrder !== 0) { - this._zIndex = (this._localZOrder & 0xffff0000) >> 16; + let spaceInfo = this._spaceInfo; + this._matrix = cc.mat4(spaceInfo.localMat); + Mat4.identity(this._matrix); + this._worldMatrix = cc.mat4(spaceInfo.worldMat); + Mat4.identity(this._worldMatrix); + this._localMatDirty = LocalDirtyFlag.ALL; + this._worldMatDirty = true; + + let trs = this._trs = spaceInfo.trs; + trs[0] = 0; // position.x + trs[1] = 0; // position.y + trs[2] = 0; // position.z + trs[3] = 0; // rotation.x + trs[4] = 0; // rotation.y + trs[5] = 0; // rotation.z + trs[6] = 1; // rotation.w + trs[7] = 1; // scale.x + trs[8] = 1; // scale.y + trs[9] = 1; // scale.z + }, + + _backDataIntoPool () { + if (!(CC_EDITOR || CC_TEST)) { + // push back to pool + nodeMemPool.push(this._spaceInfo); + this._matrix = null; + this._worldMatrix = null; + this._trs = null; + this._spaceInfo = null; } + }, - // TODO: remove _rotationX & _rotationY in future version, 3.0 ? - // Update quaternion from rotation, when upgrade from 1.x to 2.0 - // If rotation x & y is 0 in old version, then update rotation from default quaternion is ok too - let quat = this._quat; - if ((this._rotationX !== 0 || this._rotationY !== 0) && - (quat.x === 0 && quat.y === 0 && quat.z === 0 && quat.w === 1)) { - if (this._rotationX === this._rotationY) { - math.quat.fromEuler(quat, 0, 0, -this._rotationX); - } - else { - math.quat.fromEuler(quat, this._rotationX, this._rotationY, 0); - } + _toEuler () { + if (this.is3DNode) { + Trs.toEuler(this._eulerAngles, this._trs); } - // Update rotation from quaternion else { - this._syncEulerAngles(); + let z = Math.asin(this._trs[5]) / ONE_DEGREE * 2; + Vec3.set(this._eulerAngles, 0, 0, z); } + }, - // Upgrade from 2.0.0 preview 4 & earlier versions - // TODO: Remove after final version - if (this._color.a < 255 && this._opacity === 255) { - this._opacity = this._color.a; - this._color.a = 255; + _fromEuler () { + if (this.is3DNode) { + Trs.fromEuler(this._trs, this._eulerAngles); + } + else { + Trs.fromAngleZ(this._trs, this._eulerAngles.z); } }, - /* - * The initializer for Node which will be called before all components onLoad - */ - _onBatchCreated () { - this._upgrade_1x_to_2x(); - - this._updateOrderOfArrival(); + _initProperties () { + if (this._is3DNode) { + this._update3DFunction(); + } - let prefabInfo = this._prefab; - if (prefabInfo && prefabInfo.sync && prefabInfo.root === this) { - if (CC_DEV) { - // TODO - remove all usage of _synced - cc.assert(!prefabInfo._synced, 'prefab should not synced'); + let trs = this._trs; + if (trs) { + let desTrs = trs; + trs = this._trs = this._spaceInfo.trs; + // just adapt to old trs + if (desTrs.length === 11) { + trs.set(desTrs.subarray(1)); + } else { + trs.set(desTrs); } - PrefabHelper.syncWithPrefab(this); + } else { + trs = this._trs = this._spaceInfo.trs; } - if (!this._activeInHierarchy) { - // deactivate ActionManager and EventManager by default - if (ActionManagerExist) { - cc.director.getActionManager().pauseTarget(this); + if (CC_EDITOR) { + if (this._skewX !== 0 || this._skewY !== 0) { + var NodeUtils = Editor.require('scene://utils/node'); + cc.warn("`cc.Node.skewX/Y` is deprecated since v2.2.1, please use 3D node instead.", `Node: ${NodeUtils.getNodePath(this)}.`); } - eventManager.pauseTarget(this); } - let children = this._children; - for (let i = 0, len = children.length; i < len; i++) { - children[i]._onBatchCreated(); - } + this._fromEuler(); - if (children.length > 0) { - this._renderFlag |= RenderFlow.FLAG_CHILDREN; + if (CC_JSB && CC_NATIVERENDERER) { + this._renderFlag |= RenderFlow.FLAG_TRANSFORM | RenderFlow.FLAG_OPACITY_COLOR; } }, - // the same as _onBatchCreated but untouch prefab - _onBatchRestored () { - this._upgrade_1x_to_2x(); + /* + * The initializer for Node which will be called before all components onLoad + */ + _onBatchCreated (dontSyncChildPrefab) { + this._initProperties(); - if (!this._activeInHierarchy) { - // deactivate ActionManager and EventManager by default - - // ActionManager may not be inited in the editor worker. - let manager = cc.director.getActionManager(); - manager && manager.pauseTarget(this); + // Fixed a bug where children and parent node groups were forced to synchronize, instead of only synchronizing `_cullingMask` value + this._cullingMask = 1 << _getActualGroupIndex(this); + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy && this._proxy.updateCullingMask(); + } + if (!this._activeInHierarchy) { + if (CC_EDITOR ? cc.director.getActionManager() : ActionManagerExist) { + // deactivate ActionManager and EventManager by default + cc.director.getActionManager().pauseTarget(this); + } eventManager.pauseTarget(this); } - var children = this._children; - for (var i = 0, len = children.length; i < len; i++) { - children[i]._onBatchRestored(); + let children = this._children; + for (let i = 0, len = children.length; i < len; i++) { + let child = children[i]; + if (!dontSyncChildPrefab) { + // sync child prefab + let prefabInfo = child._prefab; + if (prefabInfo && prefabInfo.sync && prefabInfo.root === child) { + PrefabHelper.syncWithPrefab(child); + } + child._updateOrderOfArrival(); + } + child._onBatchCreated(dontSyncChildPrefab); } if (children.length > 0) { this._renderFlag |= RenderFlow.FLAG_CHILDREN; } + + if (CC_JSB && CC_NATIVERENDERER) { + this._proxy.initNative(); + } }, // EVENT TARGET + _checkListenerMask () { + // Because Mask may be nested, need to find all the Mask components in the parent node. + // The click area must satisfy all Masks to trigger the click. + if (this._touchListener) { + var mask = this._touchListener.mask = _searchComponentsInParent(this, cc.Mask); + if (this._mouseListener) { + this._mouseListener.mask = mask; + } + } else if (this._mouseListener) { + this._mouseListener.mask = _searchComponentsInParent(this, cc.Mask); + } + }, _checknSetupSysEvent (type) { let newAdded = false; @@ -1368,7 +1961,7 @@ var Node = cc.Class({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, owner: this, - mask: _searchMaskInParent(this), + mask: _searchComponentsInParent(this, cc.Mask), onTouchBegan: _touchStartHandler, onTouchMoved: _touchMoveHandler, onTouchEnded: _touchEndHandler, @@ -1385,7 +1978,7 @@ var Node = cc.Class({ event: cc.EventListener.MOUSE, _previousIn: false, owner: this, - mask: _searchMaskInParent(this), + mask: _searchComponentsInParent(this, cc.Mask), onMouseDown: _mouseDownHandler, onMouseMove: _mouseMoveHandler, onMouseUp: _mouseUpHandler, @@ -1425,7 +2018,7 @@ var Node = cc.Class({ * 鼠标或触摸事件会被系统调用 dispatchEvent 方法触发,触发的过程包含三个阶段:
* 1. 捕获阶段:派发事件给捕获目标(通过 `_getCapturingTargets` 获取),比如,节点树中注册了捕获阶段的父节点,从根节点开始派发直到目标节点。
* 2. 目标阶段:派发给目标节点的监听器。
- * 3. 冒泡阶段:派发事件给冒泡目标(通过 `_getBubblingTargets` 获取),比如,节点树中注册了冒泡阶段的父节点,从目标节点开始派发知道根节点。
+ * 3. 冒泡阶段:派发事件给冒泡目标(通过 `_getBubblingTargets` 获取),比如,节点树中注册了冒泡阶段的父节点,从目标节点开始派发直到根节点。
* 同时您可以将事件派发到父节点或者通过调用 stopPropagation 拦截它。
* 推荐使用这种方式来监听节点上的触摸或鼠标事件,请不要在节点上直接使用 cc.eventManager。
* 你也可以注册自定义事件到节点上,并通过 emit 方法触发此类事件,对于这类事件,不会发生捕获冒泡阶段,只会直接派发给注册在该节点上的监听器
@@ -1450,6 +2043,7 @@ var Node = cc.Class({ * node.on(cc.Node.EventType.TOUCH_END, callback, this); * node.on(cc.Node.EventType.TOUCH_CANCEL, callback, this); * node.on(cc.Node.EventType.ANCHOR_CHANGED, callback); + * node.on(cc.Node.EventType.COLOR_CHANGED, callback); */ on (type, callback, target, useCapture) { let forDispatch = this._checknSetupSysEvent(type); @@ -1473,6 +2067,9 @@ var Node = cc.Class({ case EventType.ANCHOR_CHANGED: this._eventMask |= ANCHOR_ON; break; + case EventType.COLOR_CHANGED: + this._eventMask |= COLOR_ON; + break; } if (!this._bubblingListeners) { this._bubblingListeners = new EventTarget(); @@ -1505,7 +2102,6 @@ var Node = cc.Class({ */ once (type, callback, target, useCapture) { let forDispatch = this._checknSetupSysEvent(type); - let eventType_hasOnceListener = '__ONCE_FLAG:' + type; let listeners = null; if (forDispatch && useCapture) { @@ -1515,17 +2111,10 @@ var Node = cc.Class({ listeners = this._bubblingListeners = this._bubblingListeners || new EventTarget(); } - let hasOnceListener = listeners.hasEventListener(eventType_hasOnceListener, callback, target); - if (!hasOnceListener) { - let self = this; - let onceWrapper = function (arg1, arg2, arg3, arg4, arg5) { - self.off(type, onceWrapper, target); - listeners.remove(eventType_hasOnceListener, callback, target); - callback.call(this, arg1, arg2, arg3, arg4, arg5); - }; - this.on(type, onceWrapper, target); - listeners.add(eventType_hasOnceListener, callback, target); - } + listeners.once(type, callback, target); + listeners.once(type, () => { + this.off(type, callback, target); + }, undefined); }, _onDispatch (type, callback, target, useCapture) { @@ -1549,10 +2138,11 @@ var Node = cc.Class({ } if ( !listeners.hasEventListener(type, callback, target) ) { - listeners.add(type, callback, target); + listeners.on(type, callback, target); - if (target && target.__eventTargets) + if (target && target.__eventTargets) { target.__eventTargets.push(this); + } } return callback; @@ -1614,6 +2204,9 @@ var Node = cc.Class({ case EventType.ANCHOR_CHANGED: this._eventMask &= ~ANCHOR_ON; break; + case EventType.COLOR_CHANGED: + this._eventMask &= ~COLOR_ON; + break; } } } @@ -1633,13 +2226,13 @@ var Node = cc.Class({ else { var listeners = useCapture ? this._capturingListeners : this._bubblingListeners; if (listeners) { - listeners.remove(type, callback, target); - + listeners.off(type, callback, target); + if (target && target.__eventTargets) { js.array.fastRemove(target.__eventTargets, this); } } - + } }, @@ -1672,11 +2265,18 @@ var Node = cc.Class({ if ((this._eventMask & ANCHOR_ON) && !listeners.hasEventListener(EventType.ANCHOR_CHANGED)) { this._eventMask &= ~ANCHOR_ON; } + if ((this._eventMask & COLOR_ON) && !listeners.hasEventListener(EventType.COLOR_CHANGED)) { + this._eventMask &= ~COLOR_ON; + } } if (this._capturingListeners) { this._capturingListeners.targetOff(target); } + if (target && target.__eventTargets) { + js.array.fastRemove(target.__eventTargets, this); + } + if (this._touchListener && !_checkListeners(this, _touchEvents)) { eventManager.removeListener(this._touchListener); this._touchListener = null; @@ -1719,7 +2319,7 @@ var Node = cc.Class({ * @param {*} [arg4] - Fourth argument in callback * @param {*} [arg5] - Fifth argument in callback * @example - * + * * eventTarget.emit('fire', event); * eventTarget.emit('fire', message, emitter); */ @@ -1746,10 +2346,10 @@ var Node = cc.Class({ /** * !#en Pause node related system events registered with the current Node. Node system events includes touch and mouse events. * If recursive is set to true, then this API will pause the node system events for the node and all nodes in its sub node tree. - * Reference: http://cocos2d-x.org/docs/editors_and_tools/creator-chapters/scripting/internal-events/ + * Reference: http://docs.cocos2d-x.org/editors_and_tools/creator-chapters/scripting/internal-events/ * !#zh 暂停当前节点上注册的所有节点系统事件,节点系统事件包含触摸和鼠标事件。 * 如果传递 recursive 为 true,那么这个 API 将暂停本节点和它的子树上所有节点的节点系统事件。 - * 参考:http://cocos.com/docs/creator/scripting/internal-events.html + * 参考:https://www.cocos.com/docs/creator/scripting/internal-events.html * @method pauseSystemEvents * @param {Boolean} recursive - Whether to pause node system events on the sub node tree. * @example @@ -1762,10 +2362,10 @@ var Node = cc.Class({ /** * !#en Resume node related system events registered with the current Node. Node system events includes touch and mouse events. * If recursive is set to true, then this API will resume the node system events for the node and all nodes in its sub node tree. - * Reference: http://cocos2d-x.org/docs/editors_and_tools/creator-chapters/scripting/internal-events/ + * Reference: http://docs.cocos2d-x.org/editors_and_tools/creator-chapters/scripting/internal-events/ * !#zh 恢复当前节点上注册的所有节点系统事件,节点系统事件包含触摸和鼠标事件。 * 如果传递 recursive 为 true,那么这个 API 将恢复本节点和它的子树上所有节点的节点系统事件。 - * 参考:http://cocos.com/docs/creator/scripting/internal-events.html + * 参考:https://www.cocos.com/docs/creator/scripting/internal-events.html * @method resumeSystemEvents * @param {Boolean} recursive - Whether to resume node system events on the sub node tree. * @example @@ -1778,47 +2378,60 @@ var Node = cc.Class({ _hitTest (point, listener) { let w = this._contentSize.width, h = this._contentSize.height, - cameraPt = _vec2a, - testPt = _vec2b; - + cameraPt = _htVec3a, + testPt = _htVec3b; + let camera = cc.Camera.findCamera(this); if (camera) { - camera.getCameraToWorldPoint(point, cameraPt); + camera.getScreenToWorldPoint(point, cameraPt); } else { cameraPt.set(point); } this._updateWorldMatrix(); - math.mat4.invert(_mat4_temp, this._worldMatrix); - math.vec2.transformMat4(testPt, cameraPt, _mat4_temp); + // If scale is 0, it can't be hit. + if (!Mat4.invert(_mat4_temp, this._worldMatrix)) { + return false; + } + Vec2.transformMat4(testPt, cameraPt, _mat4_temp); testPt.x += this._anchorPoint.x * w; testPt.y += this._anchorPoint.y * h; + let hit = false; if (testPt.x >= 0 && testPt.y >= 0 && testPt.x <= w && testPt.y <= h) { + hit = true; if (listener && listener.mask) { - var mask = listener.mask; - var parent = this; - for (var i = 0; parent && i < mask.index; ++i, parent = parent.parent) { - } + let mask = listener.mask; + let parent = this; + let length = mask ? mask.length : 0; // find mask parent, should hit test it - if (parent === mask.node) { - var comp = parent.getComponent(cc.Mask); - return (comp && comp.enabledInHierarchy) ? comp._hitTest(cameraPt) : true; - } - // mask parent no longer exists - else { - listener.mask = null; - return true; + for (let i = 0, j = 0; parent && j < length; ++i, parent = parent.parent) { + let temp = mask[j]; + if (i === temp.index) { + if (parent === temp.node) { + let comp = parent.getComponent(cc.Mask); + if (comp && comp._enabled && !comp._hitTest(cameraPt)) { + hit = false; + break + } + + j++; + } else { + // mask parent no longer exists + mask.length = j; + break + } + } else if (i > temp.index) { + // mask parent no longer exists + mask.length = j; + break + } } } - else { - return true; - } - } - else { - return false; } + + return hit; }, /** @@ -1842,7 +2455,7 @@ var Node = cc.Class({ parent = parent.parent; } }, - + /** * Get all the targets listening to the supplied type of event in the target's bubbling phase. * The bubbling phase comprises any SUBSEQUENT nodes encountered on the return trip to the root of the tree. @@ -1889,8 +2502,12 @@ var Node = cc.Class({ if (!this.active) return; cc.assertID(action, 1618); - - cc.director.getActionManager().addAction(action, this, false); + let am = cc.director.getActionManager(); + if (!am._suppressDeprecation) { + am._suppressDeprecation = true; + cc.warnID(1639); + } + am.addAction(action, this, false); return action; } : emptyFunc, @@ -1946,7 +2563,7 @@ var Node = cc.Class({ * @method stopActionByTag * @param {Number} tag A tag that indicates the action to be removed. * @example - * node.stopAction(1); + * node.stopActionByTag(1); */ stopActionByTag: ActionManagerExist ? function (tag) { if (tag === cc.Action.TAG_INVALID) { @@ -2003,68 +2620,76 @@ var Node = cc.Class({ // TRANSFORM RELATED /** - * !#en Returns a copy of the position (x, y) of the node in its parent's coordinates. - * !#zh 获取节点在父节点坐标系中的位置(x, y)。 + * !#en + * Returns a copy of the position (x, y, z) of the node in its parent's coordinates. + * You can pass a cc.Vec2 or cc.Vec3 as the argument to receive the return values. + * !#zh + * 获取节点在父节点坐标系中的位置(x, y, z)。 + * 你可以传一个 cc.Vec2 或者 cc.Vec3 作为参数来接收返回值。 * @method getPosition - * @return {Vec2} The position (x, y) of the node in its parent's coordinates + * @param {Vec2|Vec3} [out] - The return value to receive position + * @return {Vec2|Vec3} The position (x, y, z) of the node in its parent's coordinates * @example * cc.log("Node Position: " + node.getPosition()); */ - getPosition () { - return new cc.Vec2(this._position); + getPosition (out) { + out = out || new Vec3(); + return Trs.toPosition(out, this._trs); }, /** * !#en - * Sets the position (x, y) of the node in its parent's coordinates.
- * Usually we use cc.v2(x, y) to compose cc.Vec2 object.
- * and Passing two numbers (x, y) is more efficient than passing cc.Vec2 object. + * Sets the position (x, y, z) of the node in its parent's coordinates.
+ * Usually we use cc.v2(x, y) to compose cc.Vec2 object, in this case, position.z will become 0.
+ * and passing two numbers (x, y) is more efficient than passing cc.Vec2 object, in this case, position.z will remain unchanged. + * For 3D node we can use cc.v3(x, y, z) to compose cc.Vec3 object,
+ * and passing three numbers (x, y, z) is more efficient than passing cc.Vec3 object. * !#zh * 设置节点在父节点坐标系中的位置。
- * 可以通过两种方式设置坐标点:
- * 1. 传入 2 个数值 x 和 y。
- * 2. 传入 cc.v2(x, y) 类型为 cc.Vec2 的对象。 + * 可以通过下面的方式设置坐标点:
+ * 1. 传入 2 个数值 x, y (此时不会改变 position.z 的值)。
+ * 2. 传入 cc.v2(x, y) 类型为 cc.Vec2 的对象 (此时 position.z 的值将被设置为0)。 + * 3. 对于 3D 节点可以传入 3 个数值 x, y, z。
+ * 4. 对于 3D 节点可以传入 cc.v3(x, y, z) 类型为 cc.Vec3 的对象。 * @method setPosition - * @param {Vec2|Number} newPosOrX - X coordinate for position or the position (x, y) of the node in coordinates + * @param {Vec2|Vec3|Number} x - X coordinate for position or the position object * @param {Number} [y] - Y coordinate for position - * @example {@link cocos2d/core/utils/base-node/setPosition.js} + * @param {Number} [z] - Z coordinate for position */ - setPosition (newPosOrX, y) { - var x; + setPosition (newPosOrX, y, z) { + let x; if (y === undefined) { x = newPosOrX.x; y = newPosOrX.y; + z = newPosOrX.z; } else { x = newPosOrX; } - var locPosition = this._position; - if (locPosition.x === x && locPosition.y === y) { + let trs = this._trs; + + if (z === undefined) { + z = trs[2]; + } + + if (trs[0] === x && trs[1] === y && trs[2] === z) { return; } if (CC_EDITOR) { - var oldPosition = new cc.Vec2(locPosition); + var oldPosition = new cc.Vec3(trs[0], trs[1], trs[2]); } - if (!CC_EDITOR || isFinite(x)) { - locPosition.x = x; - } - else { - return cc.error(ERR_INVALID_NUMBER, 'x of new position'); - } - if (!CC_EDITOR || isFinite(y)) { - locPosition.y = y; - } - else { - return cc.error(ERR_INVALID_NUMBER, 'y of new position'); - } - this.setLocalDirty(LocalDirtyFlag.POSITION); - this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM; + + trs[0] = x; + trs[1] = y; + trs[2] = z; + + this.setLocalDirty(LocalDirtyFlag.ALL_POSITION); + !CC_NATIVERENDERER && (this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM); // fast check event if (this._eventMask & POSITION_ON) { - // send event if (CC_EDITOR) { this.emit(EventType.POSITION_CHANGED, oldPosition); } @@ -2077,42 +2702,75 @@ var Node = cc.Class({ /** * !#en * Returns the scale factor of the node. - * Assertion will fail when scale x != scale y. - * !#zh 获取节点的缩放。当 X 轴和 Y 轴有相同的缩放数值时。 + * Need pass a cc.Vec2 or cc.Vec3 as the argument to receive the return values. + * !#zh 获取节点的缩放,需要传一个 cc.Vec2 或者 cc.Vec3 作为参数来接收返回值。 * @method getScale - * @return {Number} The scale factor + * @param {Vec2|Vec3} out + * @return {Vec2|Vec3} The scale factor * @example - * cc.log("Node Scale: " + node.getScale()); + * cc.log("Node Scale: " + node.getScale(cc.v3())); */ - getScale () { - if (this._scale.x !== this._scale.y) - cc.logID(1603); - return this._scale.x; + getScale (out) { + if (out !== undefined) { + return Trs.toScale(out, this._trs); + } + else { + cc.errorID(1400, 'cc.Node.getScale', 'cc.Node.scale or cc.Node.getScale(cc.Vec3)'); + return this._trs[7]; + } }, /** - * !#en Sets the scale factor of the node. 1.0 is the default scale factor. This function can modify the X and Y scale at the same time. - * !#zh 设置节点的缩放比例,默认值为 1.0。这个函数可以在同一时间修改 X 和 Y 缩放。 + * !#en + * Sets the scale of axis in local coordinates of the node. + * You can operate 2 axis in 2D node, and 3 axis in 3D node. + * When only (x, y) is passed, the value of scale.z will not be changed. + * When a Vec2 is passed in, the value of scale.z will be set to 0. + * !#zh + * 设置节点在本地坐标系中坐标轴上的缩放比例。 + * 2D 节点可以操作两个坐标轴,而 3D 节点可以操作三个坐标轴。 + * 当只传入 (x, y) 时,scale.z 的值不会被改变。 + * 当只传入 Vec2 对象时,scale.z 的值将被设置为0。 * @method setScale - * @param {Number|Vec2} scaleX - scaleX or scale - * @param {Number} [scaleY] + * @param {Number|Vec2|Vec3} x - scaleX or scale object + * @param {Number} [y] + * @param {Number} [z] * @example - * node.setScale(cc.v2(1, 1)); - * node.setScale(1); - */ - setScale (x, y) { - if (x && typeof x !== 'number') { - y = x.y; - x = x.x; + * node.setScale(cc.v2(2, 2)); // Notice: scaleZ will be 0 + * node.setScale(cc.v3(2, 2, 2)); // for 3D node + * node.setScale(2); + */ + setScale (newScaleOrX, y, z) { + let x; + // only one parameter, and it's a Vec2/Vec3: + if (newScaleOrX && typeof newScaleOrX !== 'number') { + x = newScaleOrX.x; + y = newScaleOrX.y; + z = newScaleOrX.z; + } + // only one parameter, and it's a number: + else if (newScaleOrX !== undefined && y === undefined) { + x = newScaleOrX; + y = newScaleOrX; + z = newScaleOrX; + } + // two or three paramters: + else { + x = newScaleOrX; } - else if (y === undefined) { - y = x; + + let trs = this._trs; + + if (z === undefined) { + z = trs[9]; } - if (this._scale.x !== x || this._scale.y !== y) { - this._scale.x = x; - this._scale.y = y; - this.setLocalDirty(LocalDirtyFlag.SCALE); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + + if (trs[7] !== x || trs[8] !== y || trs[9] !== z) { + trs[7] = x; + trs[8] = y; + trs[9] = z; + this.setLocalDirty(LocalDirtyFlag.ALL_SCALE); + !CC_NATIVERENDERER && (this._renderFlag |= RenderFlow.FLAG_TRANSFORM); if (this._eventMask & SCALE_ON) { this.emit(EventType.SCALE_CHANGED); @@ -2121,32 +2779,67 @@ var Node = cc.Class({ }, /** - * !#en Get rotation of node (along z axi). - * !#zh 获取该节点以局部坐标系 Z 轴为轴进行旋转的角度。 + * !#en + * Get rotation of node (in quaternion). + * Need pass a cc.Quat as the argument to receive the return values. + * !#zh + * 获取该节点的 quaternion 旋转角度,需要传一个 cc.Quat 作为参数来接收返回值。 * @method getRotation - * @param {Number} rotation Degree rotation value + * @param {Quat} out + * @return {Quat} Quaternion object represents the rotation */ - getRotation () { - return this._rotationX; + getRotation (out) { + if (out instanceof Quat) { + return Trs.toRotation(out, this._trs); + } + else { + if (CC_DEBUG) { + cc.warn("`cc.Node.getRotation()` is deprecated since v2.1.0, please use `-cc.Node.angle` instead. (`this.node.getRotation()` -> `-this.node.angle`)"); + } + return -this.angle; + } }, - /** - * !#en Set rotation of node (along z axi). - * !#zh 设置该节点以局部坐标系 Z 轴为轴进行旋转的角度。 + * !#en Set rotation of node (in quaternion). + * !#zh 设置该节点的 quaternion 旋转角度。 * @method setRotation - * @param {Number} rotation Degree rotation value - */ - setRotation (value) { - if (this._rotationX !== value || this._rotationY !== value) { - this._rotationX = this._rotationY = value; - // Update quaternion from rotation - math.quat.fromEuler(this._quat, 0, 0, -value); - this.setLocalDirty(LocalDirtyFlag.ROTATION); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + * @param {cc.Quat|Number} quat Quaternion object represents the rotation or the x value of quaternion + * @param {Number} [y] y value of quternion + * @param {Number} [z] z value of quternion + * @param {Number} [w] w value of quternion + */ + setRotation (rotation, y, z, w) { + if (typeof rotation === 'number' && y === undefined) { + if (CC_DEBUG) { + cc.warn("`cc.Node.setRotation(degree)` is deprecated since v2.1.0, please set `-cc.Node.angle` instead. (`this.node.setRotation(x)` -> `this.node.angle = -x`)"); + } + this.angle = -rotation; + } + else { + let x = rotation; + if (y === undefined) { + x = rotation.x; + y = rotation.y; + z = rotation.z; + w = rotation.w; + } + + let trs = this._trs; + if (trs[3] !== x || trs[4] !== y || trs[5] !== z || trs[6] !== w) { + trs[3] = x; + trs[4] = y; + trs[5] = z; + trs[6] = w; + this.setLocalDirty(LocalDirtyFlag.ALL_ROTATION); + + if (this._eventMask & ROTATION_ON) { + this.emit(EventType.ROTATION_CHANGED); + } - if (this._eventMask & ROTATION_ON) { - this.emit(EventType.ROTATION_CHANGED); + if (CC_EDITOR) { + this._toEuler(); + } } } }, @@ -2268,7 +2961,7 @@ var Node = cc.Class({ locAnchorPoint.x = point; locAnchorPoint.y = y; } - this.setLocalDirty(LocalDirtyFlag.POSITION); + this.setLocalDirty(LocalDirtyFlag.ALL_POSITION); if (this._eventMask & ANCHOR_ON) { this.emit(EventType.ANCHOR_CHANGED); } @@ -2284,40 +2977,49 @@ var Node = cc.Class({ if (this._parent) { this._parent._invTransformPoint(out, pos); } else { - math.vec3.copy(out, pos); + Vec3.copy(out, pos); } + let ltrs = this._trs; // out = parent_inv_pos - pos - math.vec3.sub(out, out, this._position); + Trs.toPosition(_tpVec3a, ltrs); + Vec3.sub(out, out, _tpVec3a); // out = inv(rot) * out - math.quat.conjugate(_quat_temp, this._quat); - math.vec3.transformQuat(out, out, _quat_temp); + Trs.toRotation(_tpQuata, ltrs); + Quat.conjugate(_tpQuatb, _tpQuata); + Vec3.transformQuat(out, out, _tpQuatb); // out = (1/scale) * out - math.vec3.inverseSafe(_vec3_temp, this._scale); - math.vec3.mul(out, out, _vec3_temp); + Trs.toScale(_tpVec3a, ltrs); + Vec3.inverseSafe(_tpVec3b, _tpVec3a); + Vec3.mul(out, out, _tpVec3b); return out; }, - + /* * Calculate and return world position. * This is not a public API yet, its usage could be updated - * @method getWorldPos + * @method getWorldPosition * @param {Vec3} out * @return {Vec3} */ - getWorldPos (out) { - math.vec3.copy(out, this._position); + getWorldPosition (out) { + Trs.toPosition(out, this._trs); let curr = this._parent; + let ltrs; while (curr) { + ltrs = curr._trs; // out = parent_scale * pos - math.vec3.mul(out, out, curr._scale); + Trs.toScale(_gwpVec3, ltrs); + Vec3.mul(out, out, _gwpVec3); // out = parent_quat * out - math.vec3.transformQuat(out, out, curr._quat); + Trs.toRotation(_gwpQuat, ltrs); + Vec3.transformQuat(out, out, _gwpQuat); // out = out + pos - math.vec3.add(out, out, curr._position); + Trs.toPosition(_gwpVec3, ltrs); + Vec3.add(out, out, _gwpVec3); curr = curr._parent; } return out; @@ -2326,18 +3028,23 @@ var Node = cc.Class({ /* * Set world position. * This is not a public API yet, its usage could be updated - * @method setWorldPos + * @method setWorldPosition * @param {Vec3} pos */ - setWorldPos (pos) { + setWorldPosition (pos) { + let ltrs = this._trs; + if (CC_EDITOR) { + var oldPosition = new cc.Vec3(ltrs[0], ltrs[1], ltrs[2]); + } // NOTE: this is faster than invert world matrix and transform the point if (this._parent) { - this._parent._invTransformPoint(this._position, pos); + this._parent._invTransformPoint(_swpVec3, pos); } else { - math.vec3.copy(this._position, pos); + Vec3.copy(_swpVec3, pos); } - this.setLocalDirty(LocalDirtyFlag.POSITION); + Trs.fromPosition(ltrs, _swpVec3); + this.setLocalDirty(LocalDirtyFlag.ALL_POSITION); // fast check event if (this._eventMask & POSITION_ON) { @@ -2354,15 +3061,17 @@ var Node = cc.Class({ /* * Calculate and return world rotation * This is not a public API yet, its usage could be updated - * @method getWorldRot + * @method getWorldRotation * @param {Quat} out * @return {Quat} */ - getWorldRot (out) { - math.quat.copy(out, this._quat); + getWorldRotation (out) { + Trs.toRotation(_gwrQuat, this._trs); + Quat.copy(out, _gwrQuat); let curr = this._parent; while (curr) { - math.quat.mul(out, curr._quat, out); + Trs.toRotation(_gwrQuat, curr._trs); + Quat.mul(out, _gwrQuat, out); curr = curr._parent; } return out; @@ -2371,40 +3080,86 @@ var Node = cc.Class({ /* * Set world rotation with quaternion * This is not a public API yet, its usage could be updated - * @method setWorldRot - * @param {Quat} rot + * @method setWorldRotation + * @param {Quat} val + */ + setWorldRotation (val) { + if (this._parent) { + this._parent.getWorldRotation(_swrQuat); + Quat.conjugate(_swrQuat, _swrQuat); + Quat.mul(_swrQuat, _swrQuat, val); + } + else { + Quat.copy(_swrQuat, val); + } + Trs.fromRotation(this._trs, _swrQuat); + if (CC_EDITOR) { + this._toEuler(); + } + this.setLocalDirty(LocalDirtyFlag.ALL_ROTATION); + }, + + /* + * Calculate and return world scale + * This is not a public API yet, its usage could be updated + * @method getWorldScale + * @param {Vec3} out + * @return {Vec3} + */ + getWorldScale (out) { + Trs.toScale(_gwsVec3, this._trs); + Vec3.copy(out, _gwsVec3); + let curr = this._parent; + while (curr) { + Trs.toScale(_gwsVec3, curr._trs); + Vec3.mul(out, out, _gwsVec3); + curr = curr._parent; + } + return out; + }, + + /* + * Set world scale with vec3 + * This is not a public API yet, its usage could be updated + * @method setWorldScale + * @param {Vec3} scale */ - setWorldRot (quat) { + setWorldScale (scale) { if (this._parent) { - this._parent.getWorldRot(this._quat); - math.quat.conjugate(this._quat, this._quat); - math.quat.mul(this._quat, this._quat, quat); + this._parent.getWorldScale(_swsVec3); + Vec3.div(_swsVec3, scale, _swsVec3); } else { - math.quat.copy(this._quat, quat); + Vec3.copy(_swsVec3, scale); } - this.setLocalDirty(LocalDirtyFlag.ROTATION); + Trs.fromScale(this._trs, _swsVec3); + this.setLocalDirty(LocalDirtyFlag.ALL_SCALE); }, getWorldRT (out) { - let opos = _vec3_temp; - let orot = _quat_temp; - math.vec3.copy(opos, this._position); - math.quat.copy(orot, this._quat); + let opos = _gwrtVec3a; + let orot = _gwrtQuata; + let ltrs = this._trs; + Trs.toPosition(opos, ltrs); + Trs.toRotation(orot, ltrs); let curr = this._parent; while (curr) { + ltrs = curr._trs; // opos = parent_lscale * lpos - math.vec3.mul(opos, opos, curr._scale); + Trs.toScale(_gwrtVec3b, ltrs); + Vec3.mul(opos, opos, _gwrtVec3b); // opos = parent_lrot * opos - math.vec3.transformQuat(opos, opos, curr._quat); + Trs.toRotation(_gwrtQuatb, ltrs); + Vec3.transformQuat(opos, opos, _gwrtQuatb); // opos = opos + lpos - math.vec3.add(opos, opos, curr._position); + Trs.toPosition(_gwrtVec3b, ltrs); + Vec3.add(opos, opos, _gwrtVec3b); // orot = lrot * orot - math.quat.mul(orot, curr._quat, orot); + Quat.mul(orot, _gwrtQuatb, orot); curr = curr._parent; } - math.mat4.fromRT(out, orot, opos); + Mat4.fromRT(out, orot, opos); return out; }, @@ -2416,118 +3171,34 @@ var Node = cc.Class({ * @param {Vec3} [up] - default is (0,1,0) */ lookAt (pos, up) { - this.getWorldPos(_vec3_temp); - math.vec3.sub(_vec3_temp, _vec3_temp, pos); // NOTE: we use -z for view-dir - math.vec3.normalize(_vec3_temp, _vec3_temp); - math.quat.fromViewUp(_quat_temp, _vec3_temp, up); - - this.setWorldRot(_quat_temp); - }, + this.getWorldPosition(_laVec3); + Vec3.sub(_laVec3, _laVec3, pos); // NOTE: we use -z for view-dir + Vec3.normalize(_laVec3, _laVec3); + Quat.fromViewUp(_laQuat, _laVec3, up); - _updateLocalMatrix () { - let dirtyFlag = this._localMatDirty; - if (!dirtyFlag) return; - - // Update transform - let t = this._matrix; - //math.mat4.fromRTS(t, this._quat, this._position, this._scale); - - if (dirtyFlag & (LocalDirtyFlag.RT | LocalDirtyFlag.SKEW)) { - let hasRotation = this._rotationX || this._rotationY; - let hasSkew = this._skewX || this._skewY; - let sx = this._scale.x, sy = this._scale.y; - - if (hasRotation || hasSkew) { - let a = 1, b = 0, c = 0, d = 1; - // rotation - if (hasRotation) { - let rotationRadiansX = this._rotationX * ONE_DEGREE; - c = Math.sin(rotationRadiansX); - d = Math.cos(rotationRadiansX); - if (this._rotationY === this._rotationX) { - a = d; - b = -c; - } - else { - let rotationRadiansY = this._rotationY * ONE_DEGREE; - a = Math.cos(rotationRadiansY); - b = -Math.sin(rotationRadiansY); - } - } - // scale - t.m00 = a *= sx; - t.m01 = b *= sx; - t.m04 = c *= sy; - t.m05 = d *= sy; - // skew - if (hasSkew) { - let a = t.m00, b = t.m01, c = t.m04, d = t.m05; - let skx = Math.tan(this._skewX * ONE_DEGREE); - let sky = Math.tan(this._skewY * ONE_DEGREE); - if (skx === Infinity) - skx = 99999999; - if (sky === Infinity) - sky = 99999999; - t.m00 = a + c * sky; - t.m01 = b + d * sky; - t.m04 = c + a * skx; - t.m05 = d + b * skx; - } - } - else { - t.m00 = sx; - t.m01 = 0; - t.m04 = 0; - t.m05 = sy; - } - } - - // position - t.m12 = this._position.x; - t.m13 = this._position.y; - - this._localMatDirty = 0; - // Register dirty status of world matrix so that it can be recalculated - this._worldMatDirty = true; + this.setWorldRotation(_laQuat); }, + _updateLocalMatrix: updateLocalMatrix2D, + _calculWorldMatrix () { // Avoid as much function call as possible - if (this._localMatDirty) { + if (this._localMatDirty & LocalDirtyFlag.TRSS) { this._updateLocalMatrix(); } - + // Assume parent world matrix is correct let parent = this._parent; if (parent) { this._mulMat(this._worldMatrix, parent._worldMatrix, this._matrix); } else { - math.mat4.copy(this._worldMatrix, this._matrix); + Mat4.copy(this._worldMatrix, this._matrix); } this._worldMatDirty = false; }, - _mulMat (out, a, b) { - let aa=a.m00, ab=a.m01, ac=a.m04, ad=a.m05, atx=a.m12, aty=a.m13; - let ba=b.m00, bb=b.m01, bc=b.m04, bd=b.m05, btx=b.m12, bty=b.m13; - if (ab !== 0 || ac !== 0) { - out.m00 = ba * aa + bb * ac; - out.m01 = ba * ab + bb * ad; - out.m04 = bc * aa + bd * ac; - out.m05 = bc * ab + bd * ad; - out.m12 = aa * btx + ac * bty + atx; - out.m13 = ab * btx + ad * bty + aty; - } - else { - out.m00 = ba * aa; - out.m01 = bb * ad; - out.m04 = bc * aa; - out.m05 = bd * ad; - out.m12 = aa * btx + atx; - out.m13 = ad * bty + aty; - } - }, + _mulMat: mulMat2D, _updateWorldMatrix () { if (this._parent) { @@ -2544,8 +3215,15 @@ var Node = cc.Class({ }, setLocalDirty (flag) { - this._localMatDirty = this._localMatDirty | flag; + this._localMatDirty |= flag; this._worldMatDirty = true; + + if (flag === LocalDirtyFlag.ALL_POSITION || flag === LocalDirtyFlag.POSITION) { + this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM; + } + else { + this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + } }, setWorldDirty () { @@ -2565,9 +3243,9 @@ var Node = cc.Class({ */ getLocalMatrix (out) { this._updateLocalMatrix(); - return math.mat4.copy(out, this._matrix); + return Mat4.copy(out, this._matrix); }, - + /** * !#en * Get the world transform matrix (4x4) @@ -2581,10 +3259,67 @@ var Node = cc.Class({ */ getWorldMatrix (out) { this._updateWorldMatrix(); - return math.mat4.copy(out, this._worldMatrix); + return Mat4.copy(out, this._worldMatrix); + }, + + /** + * !#en + * Converts a Point to node (local) space coordinates. + * !#zh + * 将一个点转换到节点 (局部) 空间坐标系。 + * @method convertToNodeSpaceAR + * @param {Vec3|Vec2} worldPoint + * @param {Vec3|Vec2} [out] + * @return {Vec3|Vec2} + * @typescript + * convertToNodeSpaceAR(worldPoint: T, out?: T): T + * @example + * var newVec2 = node.convertToNodeSpaceAR(cc.v2(100, 100)); + * var newVec3 = node.convertToNodeSpaceAR(cc.v3(100, 100, 100)); + */ + convertToNodeSpaceAR (worldPoint, out) { + this._updateWorldMatrix(); + Mat4.invert(_mat4_temp, this._worldMatrix); + + if (worldPoint instanceof cc.Vec2) { + out = out || new cc.Vec2(); + return Vec2.transformMat4(out, worldPoint, _mat4_temp); + } + else { + out = out || new cc.Vec3(); + return Vec3.transformMat4(out, worldPoint, _mat4_temp); + } }, /** + * !#en + * Converts a Point in node coordinates to world space coordinates. + * !#zh + * 将节点坐标系下的一个点转换到世界空间坐标系。 + * @method convertToWorldSpaceAR + * @param {Vec3|Vec2} nodePoint + * @param {Vec3|Vec2} [out] + * @return {Vec3|Vec2} + * @typescript + * convertToWorldSpaceAR(nodePoint: T, out?: T): T + * @example + * var newVec2 = node.convertToWorldSpaceAR(cc.v2(100, 100)); + * var newVec3 = node.convertToWorldSpaceAR(cc.v3(100, 100, 100)); + */ + convertToWorldSpaceAR (nodePoint, out) { + this._updateWorldMatrix(); + if (nodePoint instanceof cc.Vec2) { + out = out || new cc.Vec2(); + return Vec2.transformMat4(out, nodePoint, this._worldMatrix); + } + else { + out = out || new cc.Vec3(); + return Vec3.transformMat4(out, nodePoint, this._worldMatrix); + } + }, + +// OLD TRANSFORM ACCESS APIs + /** * !#en Converts a Point to node (local) space coordinates then add the anchor point position. * So the return position will be related to the left bottom corner of the node's bounding box. * This equals to the API behavior of cocos2d-x, you probably want to use convertToNodeSpaceAR instead @@ -2592,6 +3327,7 @@ var Node = cc.Class({ * 也就是说返回的坐标是相对于节点包围盒左下角的坐标。
* 这个 API 的设计是为了和 cocos2d-x 中行为一致,更多情况下你可能需要使用 convertToNodeSpaceAR。 * @method convertToNodeSpace + * @deprecated since v2.1.3 * @param {Vec2} worldPoint * @return {Vec2} * @example @@ -2599,9 +3335,9 @@ var Node = cc.Class({ */ convertToNodeSpace (worldPoint) { this._updateWorldMatrix(); - math.mat4.invert(_mat4_temp, this._worldMatrix); + Mat4.invert(_mat4_temp, this._worldMatrix); let out = new cc.Vec2(); - math.vec2.transformMat4(out, worldPoint, _mat4_temp); + Vec2.transformMat4(out, worldPoint, _mat4_temp); out.x += this._anchorPoint.x * this._contentSize.width; out.y += this._anchorPoint.y * this._contentSize.height; return out; @@ -2613,6 +3349,7 @@ var Node = cc.Class({ * !#zh 将一个相对于节点左下角的坐标位置转换到世界空间坐标系。 * 这个 API 的设计是为了和 cocos2d-x 中行为一致,更多情况下你可能需要使用 convertToWorldSpaceAR * @method convertToWorldSpace + * @deprecated since v2.1.3 * @param {Vec2} nodePoint * @return {Vec2} * @example @@ -2624,45 +3361,9 @@ var Node = cc.Class({ nodePoint.x - this._anchorPoint.x * this._contentSize.width, nodePoint.y - this._anchorPoint.y * this._contentSize.height ); - return math.vec2.transformMat4(out, out, this._worldMatrix); - }, - - /** - * !#en - * Converts a Point to node (local) space coordinates in which the anchor point is the origin position. - * !#zh - * 将一个点转换到节点 (局部) 空间坐标系,这个坐标系以锚点为原点。 - * @method convertToNodeSpaceAR - * @param {Vec2} worldPoint - * @return {Vec2} - * @example - * var newVec2 = node.convertToNodeSpaceAR(cc.v2(100, 100)); - */ - convertToNodeSpaceAR (worldPoint) { - this._updateWorldMatrix(); - math.mat4.invert(_mat4_temp, this._worldMatrix); - let out = new cc.Vec2(); - return math.vec2.transformMat4(out, worldPoint, _mat4_temp); - }, - - /** - * !#en - * Converts a Point in node coordinates to world space coordinates. - * !#zh - * 将节点坐标系下的一个点转换到世界空间坐标系。 - * @method convertToWorldSpaceAR - * @param {Vec2} nodePoint - * @return {Vec2} - * @example - * var newVec2 = node.convertToWorldSpaceAR(cc.v2(100, 100)); - */ - convertToWorldSpaceAR (nodePoint) { - this._updateWorldMatrix(); - let out = new cc.Vec2(); - return math.vec2.transformMat4(out, nodePoint, this._worldMatrix); + return Vec2.transformMat4(out, out, this._worldMatrix); }, -// OLD TRANSFORM ACCESS APIs /** * !#en * Returns the matrix that transform the node's (local) space coordinates into the parent's space coordinates.
@@ -2681,13 +3382,13 @@ var Node = cc.Class({ out = AffineTrans.identity(); } this._updateLocalMatrix(); - + var contentSize = this._contentSize; _vec3_temp.x = -this._anchorPoint.x * contentSize.width; _vec3_temp.y = -this._anchorPoint.y * contentSize.height; - math.mat4.copy(_mat4_temp, this._matrix); - math.mat4.translate(_mat4_temp, _mat4_temp, _vec3_temp); + Mat4.copy(_mat4_temp, this._matrix); + Mat4.transform(_mat4_temp, _mat4_temp, _vec3_temp); return AffineTrans.fromMat4(out, _mat4_temp); }, @@ -2732,13 +3433,13 @@ var Node = cc.Class({ out = AffineTrans.identity(); } this._updateWorldMatrix(); - + var contentSize = this._contentSize; _vec3_temp.x = -this._anchorPoint.x * contentSize.width; _vec3_temp.y = -this._anchorPoint.y * contentSize.height; - math.mat4.copy(_mat4_temp, this._worldMatrix); - math.mat4.translate(_mat4_temp, _mat4_temp, _vec3_temp); + Mat4.copy(_mat4_temp, this._worldMatrix); + Mat4.transform(_mat4_temp, _mat4_temp, _vec3_temp); return AffineTrans.fromMat4(out, _mat4_temp); }, @@ -2786,7 +3487,7 @@ var Node = cc.Class({ out = AffineTrans.identity(); } this._updateLocalMatrix(); - math.mat4.invert(_mat4_temp, this._matrix); + Mat4.invert(_mat4_temp, this._matrix); return AffineTrans.fromMat4(out, _mat4_temp); }, @@ -2806,7 +3507,7 @@ var Node = cc.Class({ out = AffineTrans.identity(); } this._updateWorldMatrix(); - math.mat4.invert(_mat4_temp, this._worldMatrix); + Mat4.invert(_mat4_temp, this._worldMatrix); return AffineTrans.fromMat4(out, _mat4_temp); }, @@ -2837,7 +3538,7 @@ var Node = cc.Class({ convertTouchToNodeSpaceAR (touch) { return this.convertToNodeSpaceAR(touch.getLocation()); }, - + /** * !#en * Returns a "local" axis aligned bounding box of the node.
@@ -2853,9 +3554,9 @@ var Node = cc.Class({ let width = this._contentSize.width; let height = this._contentSize.height; let rect = cc.rect( - -this._anchorPoint.x * width, - -this._anchorPoint.y * height, - width, + -this._anchorPoint.x * width, + -this._anchorPoint.y * height, + width, height); return rect.transformMat4(rect, this._matrix); }, @@ -2875,25 +3576,24 @@ var Node = cc.Class({ getBoundingBoxToWorld () { if (this._parent) { this._parent._updateWorldMatrix(); - return this._getBoundingBoxTo(this._parent._worldMatrix); + return this._getBoundingBoxTo(); } else { return this.getBoundingBox(); } }, - _getBoundingBoxTo (parentMat) { - this._updateLocalMatrix(); + _getBoundingBoxTo () { let width = this._contentSize.width; let height = this._contentSize.height; let rect = cc.rect( - -this._anchorPoint.x * width, - -this._anchorPoint.y * height, - width, + -this._anchorPoint.x * width, + -this._anchorPoint.y * height, + width, height); - var parentMat = math.mat4.mul(this._worldMatrix, parentMat, this._matrix); - rect.transformMat4(rect, parentMat); + this._calculWorldMatrix(); + rect.transformMat4(rect, this._worldMatrix); //query child's BoundingBox if (!this._children) @@ -2903,7 +3603,7 @@ var Node = cc.Class({ for (var i = 0; i < locChildren.length; i++) { var child = locChildren[i]; if (child && child.active) { - var childRect = child._getBoundingBoxTo(parentMat); + var childRect = child._getBoundingBoxTo(); if (childRect) rect.union(rect, childRect); } @@ -2912,8 +3612,10 @@ var Node = cc.Class({ }, _updateOrderOfArrival () { - var arrivalOrder = ++_globalOrderOfArrival; + var arrivalOrder = this._parent ? ++this._parent._childArrivalOrder : 0; this._localZOrder = (this._localZOrder & 0xffff0000) | arrivalOrder; + + this.emit(EventType.SIBLING_ORDER_CHANGED); }, /** @@ -2977,26 +3679,35 @@ var Node = cc.Class({ */ sortAllChildren () { if (this._reorderChildDirty) { + this._reorderChildDirty = false; - var _children = this._children; + + // delay update arrivalOrder before sort children + var _children = this._children, child; + // reset arrivalOrder before sort children + this._childArrivalOrder = 1; + for (let i = 0, len = _children.length; i < len; i++) { + child = _children[i]; + child._updateOrderOfArrival(); + } + + // Optimize reordering event code to fix problems with setting zindex + // https://github.com/cocos-creator/2d-tasks/issues/1186 + eventManager._setDirtyForNode(this); + if (_children.length > 1) { // insertion sort - var len = _children.length, i, j, child; - for (i = 1; i < len; i++) { + let child, child2; + for (let i = 1, count = _children.length; i < count; i++) { child = _children[i]; - j = i - 1; - - //continue moving element downwards while zOrder is smaller or when zOrder is the same but mutatedIndex is smaller - while (j >= 0) { - if (child._localZOrder < _children[j]._localZOrder) { - _children[j + 1] = _children[j]; - } else { - break; - } - j--; + let j = i; + for (; j > 0 && + (child2 = _children[j - 1])._localZOrder > child._localZOrder; j--) { + _children[j] = child2; } - _children[j + 1] = child; + _children[j] = child; } + this.emit(EventType.CHILD_REORDER, this); } cc.director.__fastOff(cc.Director.EVENT_AFTER_UPDATE, this.sortAllChildren, this); @@ -3016,25 +3727,27 @@ var Node = cc.Class({ * The node will be destroyed when deleting in the editor, * but it will be reserved and reused for undo. */ + + // restore 3d node + this.is3DNode = this.is3DNode; + if (!this._matrix) { - this._matrix = mathPools.mat4.get(); + this._matrix = cc.mat4(this._spaceInfo.localMat); + Mat4.identity(this._matrix); } if (!this._worldMatrix) { - this._worldMatrix = mathPools.mat4.get(); + this._worldMatrix = cc.mat4(this._spaceInfo.worldMat); + Mat4.identity(this._worldMatrix); } this._localMatDirty = LocalDirtyFlag.ALL; this._worldMatDirty = true; + this._fromEuler(); + this._renderFlag |= RenderFlow.FLAG_TRANSFORM; if (this._renderComponent) { - if (this._renderComponent.enabled) { - this._renderFlag |= RenderFlow.FLAG_COLOR; - this._renderComponent.markForUpdateRenderData(true); - } - else { - this._renderComponent.disableRender(); - } + this._renderComponent.markForRender(true); } if (this._children.length > 0) { @@ -3057,45 +3770,84 @@ var Node = cc.Class({ eventManager.pauseTarget(this); } }, -}); + +}; + +if (CC_EDITOR) { + // deprecated, only used to import old data in editor + js.mixin(NodeDefines.properties, { + _scaleX: { + default: undefined, + type: cc.Float, + editorOnly: true + }, + _scaleY: { + default: undefined, + type: cc.Float, + editorOnly: true + }, + }); +} + +let Node = cc.Class(NodeDefines); // 3D Node Property -/** - * !en - * Switch 2D/3D node. The 2D nodes will run faster. - * !zh - * 切换 2D/3D 节点,2D 节点会有更高的运行效率 - * @property {Boolean} is3DNode - * @default false -*/ // Node Event /** + * !#en + * The position changing event, you can listen to this event through the statement this.node.on(cc.Node.EventType.POSITION_CHANGED, callback, this); + * !#zh + * 位置变动监听事件, 通过 this.node.on(cc.Node.EventType.POSITION_CHANGED, callback, this); 进行监听。 * @event position-changed * @param {Vec2} oldPos - The old position, but this parameter is only available in editor! */ /** + * !#en + * The size changing event, you can listen to this event through the statement this.node.on(cc.Node.EventType.SIZE_CHANGED, callback, this); + * !#zh + * 尺寸变动监听事件,通过 this.node.on(cc.Node.EventType.SIZE_CHANGED, callback, this); 进行监听。 * @event size-changed * @param {Size} oldSize - The old size, but this parameter is only available in editor! */ /** + * !#en + * The anchor changing event, you can listen to this event through the statement this.node.on(cc.Node.EventType.ANCHOR_CHANGED, callback, this); + * !#zh + * 锚点变动监听事件,通过 this.node.on(cc.Node.EventType.ANCHOR_CHANGED, callback, this); 进行监听。 * @event anchor-changed */ /** + * !#en + * The adding child event, you can listen to this event through the statement this.node.on(cc.Node.EventType.CHILD_ADDED, callback, this); + * !#zh + * 增加子节点监听事件,通过 this.node.on(cc.Node.EventType.CHILD_ADDED, callback, this); 进行监听。 * @event child-added * @param {Node} child - child which have been added */ /** + * !#en + * The removing child event, you can listen to this event through the statement this.node.on(cc.Node.EventType.CHILD_REMOVED, callback, this); + * !#zh + * 删除子节点监听事件,通过 this.node.on(cc.Node.EventType.CHILD_REMOVED, callback, this); 进行监听。 * @event child-removed * @param {Node} child - child which have been removed */ /** + * !#en + * The reordering child event, you can listen to this event through the statement this.node.on(cc.Node.EventType.CHILD_REORDER, callback, this); + * !#zh + * 子节点顺序变动监听事件,通过 this.node.on(cc.Node.EventType.CHILD_REORDER, callback, this); 进行监听。 * @event child-reorder * @param {Node} node - node whose children have been reordered */ /** + * !#en + * The group changing event, you can listen to this event through the statement this.node.on(cc.Node.EventType.GROUP_CHANGED, callback, this); + * !#zh + * 节点分组变动监听事件,通过 this.node.on(cc.Node.EventType.GROUP_CHANGED, callback, this); 进行监听。 * @event group-changed * @param {Node} node - node whose group has changed */ @@ -3112,7 +3864,7 @@ var Node = cc.Class({ * 显示透明度是基于自身透明度和父节点透明度计算的。 * * @method getDisplayedOpacity - * @returns {number} displayed opacity + * @return {number} displayed opacity * @deprecated since v2.0, please use opacity property, cascade opacity is removed */ @@ -3126,7 +3878,7 @@ var Node = cc.Class({ * 显示颜色是基于自身颜色和父节点颜色计算的。 * * @method getDisplayedColor - * @returns {Color} + * @return {Color} * @deprecated since v2.0, please use color property, cascade color is removed */ @@ -3147,7 +3899,7 @@ var Node = cc.Class({ * 返回节点的不透明度值是否影响其子节点。 * @method isCascadeOpacityEnabled * @deprecated since v2.0 - * @returns {Boolean} + * @return {Boolean} */ /** @@ -3183,9 +3935,27 @@ var Node = cc.Class({ let _p = Node.prototype; -js.getset(_p, 'rotation', _p.getRotation, _p.setRotation); -js.getset(_p, 'parent', _p.getParent, _p.setParent); js.getset(_p, 'position', _p.getPosition, _p.setPosition, false, true); -js.getset(_p, 'scale', _p.getScale, _p.setScale, false, true); + +if (CC_EDITOR) { + let vec3_tmp = new Vec3(); + cc.js.getset(_p, 'worldEulerAngles', function () { + let angles = new Vec3(this._eulerAngles); + let parent = this.parent; + while (parent) { + angles.addSelf(parent._eulerAngles); + parent = parent.parent; + } + return angles; + }, function (v) { + vec3_tmp.set(v); + let parent = this.parent; + while (parent) { + vec3_tmp.subSelf(parent._eulerAngles); + parent = parent.parent; + } + this.eulerAngles = vec3_tmp; + }); +} cc.Node = module.exports = Node; diff --git a/cocos2d/core/CCPrivateNode.js b/cocos2d/core/CCPrivateNode.js index f7384570aa4..005141cb56d 100644 --- a/cocos2d/core/CCPrivateNode.js +++ b/cocos2d/core/CCPrivateNode.js @@ -1,7 +1,7 @@ /**************************************************************************** Copyright (c) 2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -27,13 +27,11 @@ const Node = require('./CCNode'); const RenderFlow = require('./renderer/render-flow'); -const math = require('./renderer/render-engine').math; +const HideInHierarchy = cc.Object.Flags.HideInHierarchy; const LocalDirtyFlag = Node._LocalDirtyFlag; const POSITION_ON = 1 << 0; -let _vec3_temp = math.vec3.create(); - /** * !#en * Class of private entities in Cocos Creator scenes.
@@ -94,10 +92,14 @@ let PrivateNode = cc.Class({ return cc.macro.MIN_ZINDEX; }, set () { - cc.warnID(1638); }, override: true }, + showInEditor: { + default: false, + editorOnly: true, + override: true + } }, /** @@ -107,11 +109,14 @@ let PrivateNode = cc.Class({ ctor (name) { this._localZOrder = cc.macro.MIN_ZINDEX << 16; this._originPos = cc.v2(); + if (CC_EDITOR) { + this._objFlags |= HideInHierarchy; + } }, _posDirty (sendEvent) { this.setLocalDirty(LocalDirtyFlag.POSITION); - this._renderFlag |= RenderFlow.FLAG_TRANSFORM; + !CC_NATIVERENDERER && (this._renderFlag |= RenderFlow.FLAG_TRANSFORM); if (sendEvent === true && (this._eventMask & POSITION_ON)) { this.emit(Node.EventType.POSITION_CHANGED); } @@ -123,8 +128,8 @@ let PrivateNode = cc.Class({ let parent = this.parent; if (parent) { // Position correction for transform calculation - this._position.x = this._originPos.x - (parent._anchorPoint.x - 0.5) * parent._contentSize.width; - this._position.y = this._originPos.y - (parent._anchorPoint.y - 0.5) * parent._contentSize.height; + this._trs[0] = this._originPos.x - (parent._anchorPoint.x - 0.5) * parent._contentSize.width; + this._trs[1] = this._originPos.y - (parent._anchorPoint.y - 0.5) * parent._contentSize.height; } this._super(); @@ -166,7 +171,19 @@ let PrivateNode = cc.Class({ _updateOrderOfArrival() {}, }); -cc.js.getset(PrivateNode.prototype, "parent", PrivateNode.prototype.getParent, PrivateNode.prototype.setParent); -cc.js.getset(PrivateNode.prototype, "position", PrivateNode.prototype.getPosition, PrivateNode.prototype.setPosition); +let proto = PrivateNode.prototype; +cc.js.getset(proto, "parent", proto.getParent, proto.setParent); +cc.js.getset(proto, "position", proto.getPosition, proto.setPosition); + +if (CC_EDITOR) { + // check components to avoid missing node reference serialied in previous version + proto._onBatchCreated = function (dontSyncChildPrefab) { + for (let comp of this._components) { + comp.node = this; + } + + Node.prototype._onBatchCreated.call(this, dontSyncChildPrefab); + }; +} cc.PrivateNode = module.exports = PrivateNode; diff --git a/cocos2d/core/CCScene.js b/cocos2d/core/CCScene.js index da03a5c525e..43f34bfe38e 100644 --- a/cocos2d/core/CCScene.js +++ b/cocos2d/core/CCScene.js @@ -40,6 +40,10 @@ cc.Scene = cc.Class({ extends: require('./CCNode'), properties: { + _is3DNode: { + default: true, + override: true + }, /** * !#en Indicates whether all (directly or indirectly) static referenced assets of this scene are releasable by default after scene unloading. @@ -47,11 +51,7 @@ cc.Scene = cc.Class({ * @property {Boolean} autoReleaseAssets * @default false */ - autoReleaseAssets: { - default: undefined, - type: cc.Boolean - }, - + autoReleaseAssets: false, }, ctor: function () { @@ -70,7 +70,13 @@ cc.Scene = cc.Class({ }, destroy: function () { - this._super(); + if (cc.Object.prototype.destroy.call(this)) { + var children = this._children; + for (var i = 0; i < children.length; ++i) { + children[i].active = false; + } + } + this._active = false; this._activeInHierarchy = false; }, @@ -82,12 +88,7 @@ cc.Scene = cc.Class({ if (CC_TEST) { cc.assert(!this._activeInHierarchy, 'Should deactivate ActionManager and EventManager by default'); } - if (CC_EDITOR && this._prefabSyncedInLiveReload) { - this._onBatchRestored(); - } - else { - this._onBatchCreated(); - } + this._onBatchCreated(CC_EDITOR && this._prefabSyncedInLiveReload); this._inited = true; } }, diff --git a/cocos2d/core/CCScheduler.js b/cocos2d/core/CCScheduler.js index a404110d8c7..404bbfb1087 100644 --- a/cocos2d/core/CCScheduler.js +++ b/cocos2d/core/CCScheduler.js @@ -2,7 +2,7 @@ Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -346,9 +346,9 @@ cc.Scheduler.prototype = { //-----------------------public method------------------------- /** - * !en This method should be called for any target which needs to schedule tasks, and this method should be called before any scheduler API usage. + * !#en This method should be called for any target which needs to schedule tasks, and this method should be called before any scheduler API usage. * This method will add a `_id` property if it doesn't exist. - * !zh 任何需要用 Scheduler 管理任务的对象主体都应该调用这个方法,并且应该在调用任何 Scheduler API 之前调用这个方法。 + * !#zh 任何需要用 Scheduler 管理任务的对象主体都应该调用这个方法,并且应该在调用任何 Scheduler API 之前调用这个方法。 * 这个方法会给对象添加一个 `_id` 属性,如果这个属性不存在的话。 * @method enableForTarget * @param {Object} target @@ -640,7 +640,7 @@ cc.Scheduler.prototype = { * Unschedules a callback for a callback and a given target. * If you want to unschedule the "update", use `unscheduleUpdate()` * !#zh - * 根据指定的回调函数和调用对象。 + * 取消指定对象定时器。 * 如果需要取消 update 定时器,请使用 unscheduleUpdate()。 * @method unschedule * @param {Function} callback The callback to be unscheduled @@ -776,7 +776,7 @@ cc.Scheduler.prototype = { * You should NEVER call this method, unless you know what you are doing. * !#zh * 取消所有对象的所有定时器,包括系统定时器。
- * 不用调用此函数,除非你确定你在做什么。 + * 不要调用此函数,除非你确定你在做什么。 * @method unscheduleAll */ unscheduleAll: function(){ @@ -1104,4 +1104,4 @@ cc.Scheduler.PRIORITY_SYSTEM = 1 << 31; */ cc.Scheduler.PRIORITY_NON_SYSTEM = cc.Scheduler.PRIORITY_SYSTEM + 1; -module.exports = cc.Scheduler; \ No newline at end of file +module.exports = cc.Scheduler; diff --git a/cocos2d/core/asset-manager/CCAssetManager.js b/cocos2d/core/asset-manager/CCAssetManager.js new file mode 100644 index 00000000000..84111c60e41 --- /dev/null +++ b/cocos2d/core/asset-manager/CCAssetManager.js @@ -0,0 +1,831 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +const preprocess = require('./preprocess'); +const fetch = require('./fetch'); +const Cache = require('./cache'); +const helper = require('./helper'); +const releaseManager = require('./releaseManager'); +const dependUtil = require('./depend-util'); +const load = require('./load'); +const Pipeline = require('./pipeline'); +const Task = require('./task'); +const RequestItem = require('./request-item'); +const downloader = require('./downloader'); +const parser = require('./parser'); +const packManager = require('./pack-manager'); +const Bundle = require('./bundle'); +const builtins = require('./builtins'); +const factory = require('./factory'); +const { parse, combine } = require('./urlTransformer'); +const { parseParameters, asyncify } = require('./utilities'); +const { assets, files, parsed, pipeline, transformPipeline, fetchPipeline, RequestType, bundles, BuiltinBundleName } = require('./shared'); + + +/** + * @module cc + */ +/** + * !#en + * This module controls asset's behaviors and information, include loading, releasing etc. it is a singleton + * All member can be accessed with `cc.assetManager`. + * + * !#zh + * 此模块管理资源的行为和信息,包括加载,释放等,这是一个单例,所有成员能够通过 `cc.assetManager` 调用 + * + * @class AssetManager + */ +function AssetManager () { + + this._preprocessPipe = preprocess; + + this._fetchPipe = fetch; + + this._loadPipe = load; + + /** + * !#en + * Normal loading pipeline + * + * !#zh + * 正常加载管线 + * + * @property pipeline + * @type {Pipeline} + */ + this.pipeline = pipeline.append(preprocess).append(load); + + /** + * !#en + * Fetching pipeline + * + * !#zh + * 下载管线 + * + * @property fetchPipeline + * @type {Pipeline} + */ + this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch); + + /** + * !#en + * Url transformer + * + * !#zh + * Url 转换器 + * + * @property transformPipeline + * @type {Pipeline} + */ + this.transformPipeline = transformPipeline.append(parse).append(combine); + + + /** + * !#en + * The collection of bundle which is already loaded, you can remove cache with {{#crossLink "AssetManager/removeBundle:method"}}{{/crossLink}} + * + * !#zh + * 已加载 bundle 的集合, 你能通过 {{#crossLink "AssetManager/removeBundle:method"}}{{/crossLink}} 来移除缓存 + * + * @property bundles + * @type {Cache} + * @typescript + * bundles: AssetManager.Cache + */ + this.bundles = bundles; + + /** + * !#en + * The collection of asset which is already loaded, you can remove cache with {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} + * + * !#zh + * 已加载资源的集合, 你能通过 {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} 来移除缓存 + * + * @property assets + * @type {Cache} + * @typescript + * assets: AssetManager.Cache + */ + this.assets = assets; + + this._files = files; + + this._parsed = parsed; + + this.generalImportBase = ''; + + this.generalNativeBase = ''; + + /** + * !#en + * Manage relationship between asset and its dependencies + * + * !#zh + * 管理资源依赖关系 + * + * @property dependUtil + * @type {DependUtil} + */ + this.dependUtil = dependUtil; + + this._releaseManager = releaseManager; + + /** + * !#en + * Whether or not cache the loaded asset + * + * !#zh + * 是否缓存已加载的资源 + * + * @property cacheAsset + * @type {boolean} + */ + this.cacheAsset = true; + + /** + * !#en + * Whether or not load asset forcely, if it is true, asset will be loaded regardless of error + * + * !#zh + * 是否强制加载资源, 如果为 true ,加载资源将会忽略报错 + * + * @property force + * @type {boolean} + */ + this.force = false; + + /** + * !#en + * Some useful function + * + * !#zh + * 一些有用的方法 + * + * @property utils + * @type {Helper} + */ + this.utils = helper; + + /** + * !#en + * Manage all downloading task + * + * !#zh + * 管理所有下载任务 + * + * @property downloader + * @type {Downloader} + */ + this.downloader = downloader; + + /** + * !#en + * Manage all parsing task + * + * !#zh + * 管理所有解析任务 + * + * @property parser + * @type {Parser} + */ + this.parser = parser; + + /** + * !#en + * Manage internal asset + * + * !#zh + * 管理内置资源 + * + * @property builtins + * @type {Builtins} + */ + this.builtins = builtins; + + /** + * !#en + * Manage all packed asset + * + * !#zh + * 管理所有合并后的资源 + * + * @property packManager + * @type {PackManager} + */ + this.packManager = packManager; + + this.factory = factory; + + /** + * !#en + * Cache manager is a module which controls all caches downloaded from server in non-web platform. + * + * !#zh + * 缓存管理器是一个模块,在非 WEB 平台上,用于管理所有从服务器上下载下来的缓存 + * + * @property cacheManager + * @type {cc.AssetManager.CacheManager} + * @typescript + * cacheManager: cc.AssetManager.CacheManager|null + */ + this.cacheManager = null; + + /** + * !#en + * The preset of options + * + * !#zh + * 可选参数的预设集 + * + * @property presets + * @type {Object} + * @typescript + * presets: Record> + */ + this.presets = { + 'default': { + priority: 0, + }, + + 'preload': { + maxConcurrency: 2, + maxRequestsPerFrame: 2, + priority: -1, + }, + + 'scene': { + maxConcurrency: 8, + maxRequestsPerFrame: 8, + priority: 1, + }, + + 'bundle': { + maxConcurrency: 8, + maxRequestsPerFrame: 8, + priority: 2, + }, + + 'remote': { + maxRetryCount: 4 + }, + + 'script': { + maxConcurrency: 1024, + maxRequestsPerFrame: 1024, + priority: 2 + } + } + +} + +AssetManager.Pipeline = Pipeline; +AssetManager.Task = Task; +AssetManager.Cache = Cache; +AssetManager.RequestItem = RequestItem; +AssetManager.Bundle = Bundle; +AssetManager.BuiltinBundleName = BuiltinBundleName; + +AssetManager.prototype = { + + constructor: AssetManager, + + /** + * !#en + * The builtin 'main' bundle + * + * !#zh + * 内置 main 包 + * + * @property main + * @readonly + * @type {Bundle} + */ + get main () { + return bundles.get(BuiltinBundleName.MAIN); + }, + + /** + * !#en + * The builtin 'resources' bundle + * + * !#zh + * 内置 resources 包 + * + * @property resources + * @readonly + * @type {Bundle} + */ + get resources () { + return bundles.get(BuiltinBundleName.RESOURCES); + }, + + /** + * !#en + * The builtin 'internal' bundle + * + * !#zh + * 内置 internal 包 + * + * @property internal + * @readonly + * @type {Bundle} + */ + get internal () { + return bundles.get(BuiltinBundleName.INTERNAL); + }, + + /** + * !#en + * Initialize assetManager with options + * + * !#zh + * 初始化资源管理器 + * + * @method init + * @param {Object} options + * + * @typescript + * init(options: Record): void + */ + init (options) { + options = options || Object.create(null); + this._files.clear(); + this._parsed.clear(); + this._releaseManager.init(); + this.assets.clear(); + this.bundles.clear(); + this.packManager.init(); + this.downloader.init(options.bundleVers, options.server); + this.parser.init(); + this.dependUtil.init(); + this.generalImportBase = options.importBase; + this.generalNativeBase = options.nativeBase; + }, + + /** + * !#en + * Get the bundle which has been loaded + * + * !#zh + * 获取已加载的分包 + * + * @method getBundle + * @param {String} name - The name of bundle + * @return {Bundle} - The loaded bundle + * + * @example + * // ${project}/assets/test1 + * cc.assetManager.getBundle('test1'); + * + * cc.assetManager.getBundle('resources'); + * + * @typescript + * getBundle (name: string): cc.AssetManager.Bundle + */ + getBundle (name) { + return bundles.get(name); + }, + + /** + * !#en + * Remove this bundle. NOTE: The asset whthin this bundle will not be released automatically, you can call {{#crossLink "Bundle/releaseAll:method"}}{{/crossLink}} manually before remove it if you need + * + * !#zh + * 移除此包, 注意:这个包内的资源不会自动释放, 如果需要的话你可以在摧毁之前手动调用 {{#crossLink "Bundle/releaseAll:method"}}{{/crossLink}} 进行释放 + * + * @method removeBundle + * @param {Bundle} bundle - The bundle to be removed + * + * @typescript + * removeBundle(bundle: cc.AssetManager.Bundle): void + */ + removeBundle (bundle) { + bundle._destroy(); + bundles.remove(bundle.name); + }, + + /** + * !#en + * General interface used to load assets with a progression callback and a complete callback. You can achieve almost all effect you want with combination of `requests` and `options`. + * It is highly recommended that you use more simple API, such as `load`, `loadDir` etc. Every custom parameter in `options` will be distribute to each of `requests`. + * if request already has same one, the parameter in request will be given priority. Besides, if request has dependencies, `options` will distribute to dependencies too. + * Every custom parameter in `requests` will be tranfered to handler of `downloader` and `parser` as `options`. + * You can register you own handler downloader or parser to collect these custom parameters for some effect. + * + * Reserved Keyword: `uuid`, `url`, `path`, `dir`, `scene`, `type`, `priority`, `preset`, `audioLoadMode`, `ext`, `bundle`, `onFileProgress`, `maxConcurrency`, `maxRequestsPerFrame` + * `maxRetryCount`, `version`, `responseType`, `withCredentials`, `mimeType`, `timeout`, `header`, `reload`, `cacheAsset`, `cacheEnabled`, + * Please DO NOT use these words as custom options! + * + * !#zh + * 通用加载资源接口,可传入进度回调以及完成回调,通过组合 `request` 和 `options` 参数,几乎可以实现和扩展所有想要的加载效果。非常建议你使用更简单的API,例如 `load`、`loadDir` 等。 + * `options` 中的自定义参数将会分发到 `requests` 的每一项中,如果request中已存在同名的参数则以 `requests` 中为准,同时如果有其他 + * 依赖资源,则 `options` 中的参数会继续向依赖项中分发。request中的自定义参数都会以 `options` 形式传入加载流程中的 `downloader`, `parser` 的方法中, 你可以 + * 扩展 `downloader`, `parser` 收集参数完成想实现的效果。 + * + * 保留关键字: `uuid`, `url`, `path`, `dir`, `scene`, `type`, `priority`, `preset`, `audioLoadMode`, `ext`, `bundle`, `onFileProgress`, `maxConcurrency`, `maxRequestsPerFrame` + * `maxRetryCount`, `version`, `responseType`, `withCredentials`, `mimeType`, `timeout`, `header`, `reload`, `cacheAsset`, `cacheEnabled`, + * 请不要使用这些字段为自定义参数! + * + * @method loadAny + * @param {string|string[]|Object|Object[]} requests - The request you want to load + * @param {Object} [options] - Optional parameters + * @param {Function} [onProgress] - Callback invoked when progression change + * @param {Number} onProgress.finished - The number of the items that are already completed + * @param {Number} onProgress.total - The total number of the items + * @param {RequestItem} onProgress.item - The current request item + * @param {Function} [onComplete] - Callback invoked when finish loading + * @param {Error} onComplete.err - The error occured in loading process. + * @param {Object} onComplete.data - The loaded content + * + * @example + * cc.assetManager.loadAny({url: 'http://example.com/a.png'}, (err, img) => cc.log(img)); + * cc.assetManager.loadAny(['60sVXiTH1D/6Aft4MRt9VC'], (err, assets) => cc.log(assets)); + * cc.assetManager.loadAny([{ uuid: '0cbZa5Y71CTZAccaIFluuZ'}, {url: 'http://example.com/a.png'}], (err, assets) => cc.log(assets)); + * cc.assetManager.downloader.register('.asset', (url, options, onComplete) => { + * url += '?userName=' + options.userName + "&password=" + options.password; + * cc.assetManager.downloader.downloadFile(url, null, onComplete); + * }); + * cc.assetManager.parser.register('.asset', (file, options, onComplete) => { + * var json = JSON.parse(file); + * var skin = json[options.skin]; + * var model = json[options.model]; + * onComplete(null, {skin, model}); + * }); + * cc.assetManager.loadAny({ url: 'http://example.com/my.asset', skin: 'xxx', model: 'xxx', userName: 'xxx', password: 'xxx' }); + * + * @typescript + * loadAny(requests: string | string[] | Record | Record[], options: Record, onProgress: (finished: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (err: Error, data: any) => void): void + * loadAny(requests: string | string[] | Record | Record[], onProgress: (finished: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (err: Error, data: any) => void): void + * loadAny(requests: string | string[] | Record | Record[], options: Record, onComplete: (err: Error, data: any) => void): void + * loadAny(requests: string | string[] | Record | Record[], onComplete: (err: Error, data: any) => void): void + * loadAny(requests: string | string[] | Record | Record[], options: Record): void + * loadAny(requests: string | string[] | Record | Record[]): void + */ + loadAny (requests, options, onProgress, onComplete) { + var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete); + + options.preset = options.preset || 'default'; + requests = Array.isArray(requests) ? requests.concat() : requests; + let task = new Task({input: requests, onProgress, onComplete: asyncify(onComplete), options}); + pipeline.async(task); + }, + + /** + * !#en + * General interface used to preload assets with a progression callback and a complete callback.It is highly recommended that you use more simple API, such as `preloadRes`, `preloadResDir` etc. + * Everything about preload is just likes `cc.assetManager.loadAny`, the difference is `cc.assetManager.preloadAny` will only download asset but not parse asset. You need to invoke `cc.assetManager.loadAny(preloadTask)` + * to finish loading asset + * + * !#zh + * 通用预加载资源接口,可传入进度回调以及完成回调,非常建议你使用更简单的 API ,例如 `preloadRes`, `preloadResDir` 等。`preloadAny` 和 `loadAny` 几乎一样,区别在于 `preloadAny` 只会下载资源,不会去解析资源,你需要调用 `cc.assetManager.loadAny(preloadTask)` + * 来完成资源加载。 + * + * @method preloadAny + * @param {string|string[]|Object|Object[]} requests - The request you want to preload + * @param {Object} [options] - Optional parameters + * @param {Function} [onProgress] - Callback invoked when progression change + * @param {Number} onProgress.finished - The number of the items that are already completed + * @param {Number} onProgress.total - The total number of the items + * @param {RequestItem} onProgress.item - The current request item + * @param {Function} [onComplete] - Callback invoked when finish preloading + * @param {Error} onComplete.err - The error occured in preloading process. + * @param {RequestItem[]} onComplete.items - The preloaded content + * + * @example + * cc.assetManager.preloadAny('0cbZa5Y71CTZAccaIFluuZ', (err) => cc.assetManager.loadAny('0cbZa5Y71CTZAccaIFluuZ')); + * + * @typescript + * preloadAny(requests: string | string[] | Record | Record[], options: Record, onProgress: (finished: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (err: Error, items: cc.AssetManager.RequestItem[]) => void): void + * preloadAny(requests: string | string[] | Record | Record[], onProgress: (finished: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (err: Error, items: cc.AssetManager.RequestItem[]) => void): void + * preloadAny(requests: string | string[] | Record | Record[], options: Record, onComplete: (err: Error, items: cc.AssetManager.RequestItem[]) => void): void + * preloadAny(requests: string | string[] | Record | Record[], onComplete: (err: Error, items: cc.AssetManager.RequestItem[]) => void): void + * preloadAny(requests: string | string[] | Record | Record[], options: Record): void + * preloadAny(requests: string | string[] | Record | Record[]): void + */ + preloadAny (requests, options, onProgress, onComplete) { + var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete); + + options.preset = options.preset || 'preload'; + requests = Array.isArray(requests) ? requests.concat() : requests; + var task = new Task({input: requests, onProgress, onComplete: asyncify(onComplete), options}); + fetchPipeline.async(task); + }, + + /** + * !#en + * Load native file of asset, if you check the option 'Async Load Assets', you may need to load native file with this before you use the asset + * + * !#zh + * 加载资源的原生文件,如果你勾选了'延迟加载资源'选项,你可能需要在使用资源之前调用此方法来加载原生文件 + * + * @method postLoadNative + * @param {Asset} asset - The asset + * @param {Object} [options] - Some optional parameters + * @param {Function} [onComplete] - Callback invoked when finish loading + * @param {Error} onComplete.err - The error occured in loading process. + * + * @example + * cc.assetManager.postLoadNative(texture, (err) => console.log(err)); + * + * @typescript + * postLoadNative(asset: cc.Asset, options: Record, onComplete: (err: Error) => void): void + * postLoadNative(asset: cc.Asset, onComplete: (err: Error) => void): void + * postLoadNative(asset: cc.Asset, options: Record): void + * postLoadNative(asset: cc.Asset): void + */ + postLoadNative (asset, options, onComplete) { + if (!(asset instanceof cc.Asset)) throw new Error('input is not asset'); + var { options, onComplete } = parseParameters(options, undefined, onComplete); + + if (!asset._native || asset._nativeAsset) { + return asyncify(onComplete)(null); + } + + var depend = dependUtil.getNativeDep(asset._uuid); + if (depend) { + if (!bundles.has(depend.bundle)) { + var bundle = bundles.find(function (bundle) { + return bundle.getAssetInfo(asset._uuid); + }); + if (bundle) { + depend.bundle = bundle.name; + } + } + + this.loadAny(depend, options, function (err, native) { + if (!err) { + !asset._nativeAsset && (asset._nativeAsset = native); + } + else { + cc.error(err.message, err.stack); + } + onComplete && onComplete(err); + }); + } + }, + + /** + * !#en + * Load remote asset with url, such as audio, image, text and so on. + * + * !#zh + * 使用 url 加载远程资源,例如音频,图片,文本等等。 + * + * @method loadRemote + * @param {string} url - The url of asset + * @param {Object} [options] - Some optional parameters + * @param {cc.AudioClip.LoadMode} [options.audioLoadMode] - Indicate which mode audio you want to load + * @param {string} [options.ext] - If the url does not have a extension name, you can specify one manually. + * @param {Function} [onComplete] - Callback invoked when finish loading + * @param {Error} onComplete.err - The error occured in loading process. + * @param {Asset} onComplete.asset - The loaded texture + * + * @example + * cc.assetManager.loadRemote('http://www.cloud.com/test1.jpg', (err, texture) => console.log(err)); + * cc.assetManager.loadRemote('http://www.cloud.com/test2.mp3', (err, audioClip) => console.log(err)); + * cc.assetManager.loadRemote('http://www.cloud.com/test3', { ext: '.png' }, (err, texture) => console.log(err)); + * + * @typescript + * loadRemote(url: string, options: Record, onComplete: (err: Error, asset: T) => void): void + * loadRemote(url: string, onComplete: (err: Error, asset: T) => void): void + * loadRemote(url: string, options: Record): void + * loadRemote(url: string): void + */ + loadRemote (url, options, onComplete) { + var { options, onComplete } = parseParameters(options, undefined, onComplete); + + if (this.assets.has(url)) { + return asyncify(onComplete)(null, this.assets.get(url)); + } + + options.__isNative__ = true; + options.preset = options.preset || 'remote'; + this.loadAny({url}, options, null, function (err, data) { + if (err) { + cc.error(err.message, err.stack); + onComplete && onComplete(err, null); + } + else { + factory.create(url, data, options.ext || cc.path.extname(url), options, function (err, out) { + onComplete && onComplete(err, out); + }); + } + }); + }, + + /** + * !#en + * Load script + * + * !#zh + * 加载脚本 + * + * @method loadScript + * @param {string|string[]} url - Url of the script + * @param {Object} [options] - Some optional paramters + * @param {boolean} [options.async] - Indicate whether or not loading process should be async + * @param {Function} [onComplete] - Callback when script loaded or failed + * @param {Error} onComplete.err - The occurred error, null indicetes success + * + * @example + * loadScript('http://localhost:8080/index.js', null, (err) => console.log(err)); + * + * @typescript + * loadScript(url: string|string[], options: Record, onComplete: (err: Error) => void): void; + * loadScript(url: string|string[], onComplete: (err: Error) => void): void; + * loadScript(url: string|string[], options: Record): void; + * loadScript(url: string|string[]): void; + */ + loadScript (url, options, onComplete) { + var { options, onComplete } = parseParameters(options, undefined, onComplete); + options.__requestType__ = RequestType.URL; + options.preset = options.preset || 'script'; + this.loadAny(url, options, onComplete); + }, + + /** + * !#en + * load bundle + * + * !#zh + * 加载资源包 + * + * @method loadBundle + * @param {string} nameOrUrl - The name or root path of bundle + * @param {Object} [options] - Some optional paramter, same like downloader.downloadFile + * @param {string} [options.version] - The version of this bundle, you can check config.json in this bundle + * @param {Function} [onComplete] - Callback when bundle loaded or failed + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {Bundle} onComplete.bundle - The loaded bundle + * + * @example + * loadBundle('http://localhost:8080/test', null, (err, bundle) => console.log(err)); + * + * @typescript + * loadBundle(nameOrUrl: string, options: Record, onComplete: (err: Error, bundle: cc.AssetManager.Bundle) => void): void + * loadBundle(nameOrUrl: string, onComplete: (err: Error, bundle: cc.AssetManager.Bundle) => void): void + * loadBundle(nameOrUrl: string, options: Record): void + * loadBundle(nameOrUrl: string): void + */ + loadBundle (nameOrUrl, options, onComplete) { + var { options, onComplete } = parseParameters(options, undefined, onComplete); + + let bundleName = cc.path.basename(nameOrUrl); + + if (this.bundles.has(bundleName)) { + return asyncify(onComplete)(null, this.getBundle(bundleName)); + } + + options.preset = options.preset || 'bundle'; + options.ext = 'bundle'; + this.loadRemote(nameOrUrl, options, onComplete); + }, + + /** + * !#en + * Release asset and it's dependencies. + * This method will not only remove the cache of the asset in assetManager, but also clean up its content. + * For example, if you release a texture, the texture asset and its gl texture data will be freed up. + * Notice, this method may cause the texture to be unusable, if there are still other nodes use the same texture, they may turn to black and report gl errors. + * + * !#zh + * 释放资源以及其依赖资源, 这个方法不仅会从 assetManager 中删除资源的缓存引用,还会清理它的资源内容。 + * 比如说,当你释放一个 texture 资源,这个 texture 和它的 gl 贴图数据都会被释放。 + * 注意,这个函数可能会导致资源贴图或资源所依赖的贴图不可用,如果场景中存在节点仍然依赖同样的贴图,它们可能会变黑并报 GL 错误。 + * + * @method releaseAsset + * @param {Asset} asset - The asset to be released + * + * @example + * // release a texture which is no longer need + * cc.assetManager.releaseAsset(texture); + * + * @typescript + * releaseAsset(asset: cc.Asset): void + */ + releaseAsset (asset) { + releaseManager.tryRelease(asset, true); + }, + + /** + * !#en + * Release all unused assets. Refer to {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} for detailed informations. + * + * !#zh + * 释放所有没有用到的资源。详细信息请参考 {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} + * + * @method releaseUnusedAssets + * @private + * + * @typescript + * releaseUnusedAssets(): void + */ + releaseUnusedAssets () { + assets.forEach(function (asset) { + releaseManager.tryRelease(asset); + }); + }, + + /** + * !#en + * Release all assets. Refer to {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} for detailed informations. + * + * !#zh + * 释放所有资源。详细信息请参考 {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} + * + * @method releaseAll + * + * @typescript + * releaseAll(): void + */ + releaseAll () { + assets.forEach(function (asset) { + releaseManager.tryRelease(asset, true); + }); + }, + + _transform (input, options) { + var subTask = Task.create({input, options}); + var urls = []; + try { + var result = transformPipeline.sync(subTask); + for (var i = 0, l = result.length; i < l; i++) { + var item = result[i]; + var url = item.url; + item.recycle(); + urls.push(url); + } + } + catch (e) { + for (var i = 0, l = subTask.output.length; i < l; i++) { + subTask.output[i].recycle(); + } + cc.error(e.message, e.stack); + } + subTask.recycle(); + return urls.length > 1 ? urls : urls[0]; + } +}; + +cc.AssetManager = AssetManager; +/** + * @module cc + */ +/** + * @property assetManager + * @type {AssetManager} + */ +cc.assetManager = new AssetManager(); + +Object.defineProperty(cc, 'resources', { + /** + * !#en + * cc.resources is a bundle and controls all asset under assets/resources + * + * !#zh + * cc.resources 是一个 bundle,用于管理所有在 assets/resources 下的资源 + * + * @property resources + * @readonly + * @type {AssetManager.Bundle} + */ + get () { + return bundles.get(BuiltinBundleName.RESOURCES); + } +}); + + +module.exports = cc.assetManager; + +/** + * !#en + * This module controls asset's behaviors and information, include loading, releasing etc. + * All member can be accessed with `cc.assetManager`. All class or enum can be accessed with `cc.AssetManager` + * + * !#zh + * 此模块管理资源的行为和信息,包括加载,释放等,所有成员能够通过 `cc.assetManager` 调用. 所有类型或枚举能通过 `cc.AssetManager` 访问 + * + * @module cc.AssetManager + */ \ No newline at end of file diff --git a/cocos2d/core/asset-manager/builtins.js b/cocos2d/core/asset-manager/builtins.js new file mode 100644 index 00000000000..ae8cca7f8f2 --- /dev/null +++ b/cocos2d/core/asset-manager/builtins.js @@ -0,0 +1,133 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const Cache = require('./cache'); +const releaseManager = require('./releaseManager'); +const { BuiltinBundleName } = require('./shared'); + +/** + * @module cc.AssetManager + */ +/** + * !#en + * This module contains the builtin asset, it's a singleton, all member can be accessed with `cc.assetManager.builtins` + * + * !#zh + * 此模块包含内建资源,这是一个单例,所有成员能通过 `cc.assetManager.builtins` 访问 + * + * @class Builtins + */ +var builtins = { + + _assets: new Cache({ material: new Cache(), effect: new Cache() }), // builtin assets + + _loadBuiltins (name, cb) { + let dirname = name + 's'; + let builtin = this._assets.get(name); + return cc.assetManager.internal.loadDir(dirname, null, null, (err, assets) => { + if (err) { + cc.error(err.message, err.stack); + } + else { + for (let i = 0; i < assets.length; i++) { + var asset = assets[i]; + builtin.add(asset.name, asset.addRef()); + } + } + + cb(); + }); + }, + + /** + * !#en + * Initialize + * + * !#zh + * 初始化 + * + * @method init + * @param {Function} cb - Callback when finish loading built-in assets + * + * @typescript + * init (cb: () => void): void + */ + init (cb) { + this.clear(); + if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS || !cc.assetManager.bundles.has(BuiltinBundleName.INTERNAL)) { + return cb && cb(); + } + + this._loadBuiltins('effect', () => { + this._loadBuiltins('material', cb); + }); + }, + + /** + * !#en + * Get the built-in asset using specific type and name. + * + * !#zh + * 通过特定的类型和名称获取内建资源 + * + * @method getBuiltin + * @param {string} [type] - The type of asset, such as `effect` + * @param {string} [name] - The name of asset, such as `phong` + * @return {Asset|Cache} Builtin-assets + * + * @example + * cc.assetManaer.builtins.getBuiltin('effect', 'phone'); + * + * @typescript + * getBuiltin(type?: string, name?: string): cc.Asset | Cache + */ + getBuiltin (type, name) { + if (arguments.length === 0) return this._assets; + else if (arguments.length === 1) return this._assets.get(type); + else return this._assets.get(type).get(name); + }, + + /** + * !#en + * Clear all builtin assets + * + * !#zh + * 清空所有内置资源 + * + * @method clear + * + * @typescript + * clear(): void + */ + clear () { + this._assets.forEach(function (assets) { + assets.forEach(function (asset) { + releaseManager.tryRelease(asset, true); + }); + assets.clear(); + }); + } +} + +module.exports = builtins; diff --git a/cocos2d/core/asset-manager/bundle.js b/cocos2d/core/asset-manager/bundle.js new file mode 100644 index 00000000000..bd0ed68ae3c --- /dev/null +++ b/cocos2d/core/asset-manager/bundle.js @@ -0,0 +1,614 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const Config = require('./config'); +const releaseManager = require('./releaseManager'); +const { parseParameters, parseLoadResArgs } = require('./utilities'); +const { RequestType, assets, bundles } = require('./shared'); + +/** + * @module cc.AssetManager + */ + +/** + * !#en + * A bundle contains an amount of assets(includes scene), you can load, preload, release asset which is in this bundle + * + * !#zh + * 一个包含一定数量资源(包括场景)的包,你可以加载,预加载,释放此包内的资源 + * + * @class Bundle + */ +function Bundle () { + this._config = new Config(); +} + +Bundle.prototype = { + + /** + * !#en + * Create a bundle + * + * !#zh + * 创建一个 bundle + * + * @method constructor + * + * @typescript + * constructor() + */ + constructor: Bundle, + + /** + * !#en + * The name of this bundle + * + * !#zh + * 此 bundle 的名称 + * + * @property name + * @type {string} + */ + get name () { + return this._config.name; + }, + + /** + * !#en + * The dependency of this bundle + * + * !#zh + * 此 bundle 的依赖 + * + * @property deps + * @type {string[]} + */ + get deps () { + return this._config.deps; + }, + + /** + * !#en + * The root path of this bundle, such like 'http://example.com/bundle1' + * + * !#zh + * 此 bundle 的根路径, 例如 'http://example.com/bundle1' + * + * @property base + * @type {string} + */ + get base () { + return this._config.base; + }, + + /** + * !#en + * Get asset's info using path, only valid when asset is in bundle folder. + * + * !#zh + * 使用 path 获取资源的配置信息 + * + * @method getInfoWithPath + * @param {string} path - The relative path of asset, such as 'images/a' + * @param {Function} [type] - The constructor of asset, such as `cc.Texture2D` + * @returns {Object} The asset info + * + * @example + * var info = bundle.getInfoWithPath('image/a', cc.Texture2D); + * + * @typescript + * getInfoWithPath (path: string, type?: typeof cc.Asset): Record + */ + getInfoWithPath (path, type) { + return this._config.getInfoWithPath(path, type); + }, + + /** + * !#en + * Get all asset's info within specific folder + * + * !#zh + * 获取在某个指定文件夹下的所有资源信息 + * + * @method getDirWithPath + * @param {string} path - The relative path of folder, such as 'images' + * @param {Function} [type] - The constructor should be used to filter paths + * @param {Array} [out] - The output array + * @returns {Object[]} Infos + * + * @example + * var infos = []; + * bundle.getDirWithPath('images', cc.Texture2D, infos); + * + * @typescript + * getDirWithPath (path: string, type: typeof cc.Asset, out: Array>): Array> + * getDirWithPath (path: string, type: typeof cc.Asset): Array> + * getDirWithPath (path: string): Array> + */ + getDirWithPath (path, type, out) { + return this._config.getDirWithPath(path, type, out); + }, + + /** + * !#en + * Get asset's info with uuid + * + * !#zh + * 通过 uuid 获取资源信息 + * + * @method getAssetInfo + * @param {string} uuid - The asset's uuid + * @returns {Object} info + * + * @example + * var info = bundle.getAssetInfo('fcmR3XADNLgJ1ByKhqcC5Z'); + * + * @typescript + * getAssetInfo (uuid: string): Record + */ + getAssetInfo (uuid) { + return this._config.getAssetInfo(uuid); + }, + + /** + * !#en + * Get scene'info with name + * + * !#zh + * 通过场景名获取场景信息 + * + * @method getSceneInfo + * @param {string} name - The name of scene + * @return {Object} info + * + * @example + * var info = bundle.getSceneInfo('first.fire'); + * + * @typescript + * getSceneInfo(name: string): Record + */ + getSceneInfo (name) { + return this._config.getSceneInfo(name); + }, + + /** + * !#en + * Initialize this bundle with options + * + * !#zh + * 初始化此 bundle + * + * @method init + * @param {Object} options + * + * @typescript + * init(options: Record): void + */ + init (options) { + this._config.init(options); + bundles.add(options.name, this); + }, + + /** + * !#en + * Load the asset within this bundle by the path which is relative to bundle's path + * + * !#zh + * 通过相对路径加载分包中的资源。路径是相对分包文件夹路径的相对路径 + * + * @method load + * @param {String|String[]} paths - Paths of the target assets.The path is relative to the bundle's folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be loaded if this argument is supplied. + * @param {Function} [onProgress] - Callback invoked when progression change. + * @param {Number} onProgress.finish - The number of the items that are already completed. + * @param {Number} onProgress.total - The total number of the items. + * @param {RequestItem} onProgress.item - The finished request item. + * @param {Function} [onComplete] - Callback invoked when all assets loaded. + * @param {Error} onComplete.error - The error info or null if loaded successfully. + * @param {Asset|Asset[]} onComplete.assets - The loaded assets. + * + * @example + * // load the texture (${project}/assets/resources/textures/background.jpg) from resources + * cc.resources.load('textures/background', cc.Texture2D, (err, texture) => console.log(err)); + * + * // load the audio (${project}/assets/resources/music/hit.mp3) from resources + * cc.resources.load('music/hit', cc.AudioClip, (err, audio) => console.log(err)); + * + * // load the prefab (${project}/assets/bundle1/misc/character/cocos) from bundle1 folder + * bundle1.load('misc/character/cocos', cc.Prefab, (err, prefab) => console.log(err)); + * + * // load the sprite frame (${project}/assets/some/xxx/bundle2/imgs/cocos.png) from bundle2 folder + * bundle2.load('imgs/cocos', cc.SpriteFrame, null, (err, spriteFrame) => console.log(err)); + * + * @typescript + * load(paths: string, type: typeof cc.Asset, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, assets: T) => void): void + * load(paths: string[], type: typeof cc.Asset, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, assets: Array) => void): void + * load(paths: string, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, assets: T) => void): void + * load(paths: string[], onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, assets: Array) => void): void + * load(paths: string, type: typeof cc.Asset, onComplete?: (error: Error, assets: T) => void): void + * load(paths: string[], type: typeof cc.Asset, onComplete?: (error: Error, assets: Array) => void): void + * load(paths: string, onComplete?: (error: Error, assets: T) => void): void + * load(paths: string[], onComplete?: (error: Error, assets: Array) => void): void + */ + load (paths, type, onProgress, onComplete) { + var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete); + cc.assetManager.loadAny(paths, { __requestType__: RequestType.PATH, type: type, bundle: this.name, __outputAsArray__: Array.isArray(paths) }, onProgress, onComplete); + }, + + /** + * !#en + * Preload the asset within this bundle by the path which is relative to bundle's path. + * After calling this method, you still need to finish loading by calling `Bundle.load`. + * It will be totally fine to call `Bundle.load` at any time even if the preloading is not + * yet finished + * + * !#zh + * 通过相对路径预加载分包中的资源。路径是相对分包文件夹路径的相对路径。调用完后,你仍然需要通过 `Bundle.load` 来完成加载。 + * 就算预加载还没完成,你也可以直接调用 `Bundle.load`。 + * + * @method preload + * @param {String|String[]} paths - Paths of the target asset.The path is relative to bundle folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be loaded if this argument is supplied. + * @param {Function} [onProgress] - Callback invoked when progression change. + * @param {Number} onProgress.finish - The number of the items that are already completed. + * @param {Number} onProgress.total - The total number of the items. + * @param {RequestItem} onProgress.item - The finished request item. + * @param {Function} [onComplete] - Callback invoked when the resource loaded. + * @param {Error} onComplete.error - The error info or null if loaded successfully. + * @param {RequestItem[]} onComplete.items - The preloaded items. + * + * @example + * // preload the texture (${project}/assets/resources/textures/background.jpg) from resources + * cc.resources.preload('textures/background', cc.Texture2D); + * + * // preload the audio (${project}/assets/resources/music/hit.mp3) from resources + * cc.resources.preload('music/hit', cc.AudioClip); + * // wait for while + * cc.resources.load('music/hit', cc.AudioClip, (err, audioClip) => {}); + * + * * // preload the prefab (${project}/assets/bundle1/misc/character/cocos) from bundle1 folder + * bundle1.preload('misc/character/cocos', cc.Prefab); + * + * // load the sprite frame of (${project}/assets/bundle2/imgs/cocos.png) from bundle2 folder + * bundle2.preload('imgs/cocos', cc.SpriteFrame); + * // wait for while + * bundle2.load('imgs/cocos', cc.SpriteFrame, (err, spriteFrame) => {}); + * + * @typescript + * preload(paths: string|string[], type: typeof cc.Asset, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, items: RequestItem[]) => void): void + * preload(paths: string|string[], onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, items: RequestItem[]) => void): void + * preload(paths: string|string[], type: typeof cc.Asset, onComplete: (error: Error, items: RequestItem[]) => void): void + * preload(paths: string|string[], type: typeof cc.Asset): void + * preload(paths: string|string[], onComplete: (error: Error, items: RequestItem[]) => void): void + * preload(paths: string|string[]): void + */ + preload (paths, type, onProgress, onComplete) { + var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete); + cc.assetManager.preloadAny(paths, { __requestType__: RequestType.PATH, type: type, bundle: this.name }, onProgress, onComplete); + }, + + /** + * !#en + * Load all assets under a folder inside the bundle folder.
+ *
+ * Note: All asset paths in Creator use forward slashes, paths using backslashes will not work. + * + * !#zh + * 加载目标文件夹中的所有资源, 注意:路径中只能使用斜杠,反斜杠将停止工作 + * + * @method loadDir + * @param {string} dir - path of the target folder.The path is relative to the bundle folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be loaded if this argument is supplied. + * @param {Function} [onProgress] - Callback invoked when progression change. + * @param {Number} onProgress.finish - The number of the items that are already completed. + * @param {Number} onProgress.total - The total number of the items. + * @param {Object} onProgress.item - The latest request item + * @param {Function} [onComplete] - A callback which is called when all assets have been loaded, or an error occurs. + * @param {Error} onComplete.error - If one of the asset failed, the complete callback is immediately called with the error. If all assets are loaded successfully, error will be null. + * @param {Asset[]|Asset} onComplete.assets - An array of all loaded assets. + * + * @example + * // load all audios (resources/audios/) + * cc.resources.loadDir('audios', cc.AudioClip, (err, audios) => {}); + * + * // load all textures in "resources/imgs/" + * cc.resources.loadDir('imgs', cc.Texture2D, null, function (err, textures) { + * var texture1 = textures[0]; + * var texture2 = textures[1]; + * }); + * + * // load all prefabs (${project}/assets/bundle1/misc/characters/) from bundle1 folder + * bundle1.loadDir('misc/characters', cc.Prefab, (err, prefabs) => console.log(err)); + * + * // load all sprite frame (${project}/assets/some/xxx/bundle2/skills/) from bundle2 folder + * bundle2.loadDir('skills', cc.SpriteFrame, null, (err, spriteFrames) => console.log(err)); + * + * @typescript + * loadDir(dir: string, type: typeof cc.Asset, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, assets: Array) => void): void + * loadDir(dir: string, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, assets: Array) => void): void + * loadDir(dir: string, type: typeof cc.Asset, onComplete: (error: Error, assets: Array) => void): void + * loadDir(dir: string, type: typeof cc.Asset): void + * loadDir(dir: string, onComplete: (error: Error, assets: Array) => void): void + * loadDir(dir: string): void + */ + loadDir (dir, type, onProgress, onComplete) { + var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete); + cc.assetManager.loadAny(dir, { __requestType__: RequestType.DIR, type: type, bundle: this.name, __outputAsArray__: true }, onProgress, onComplete); + }, + + /** + * !#en + * Preload all assets under a folder inside the bundle folder.
After calling this method, you still need to finish loading by calling `Bundle.loadDir`. + * It will be totally fine to call `Bundle.loadDir` at any time even if the preloading is not yet finished + * + * !#zh + * 预加载目标文件夹中的所有资源。调用完后,你仍然需要通过 `Bundle.loadDir` 来完成加载。 + * 就算预加载还没完成,你也可以直接调用 `Bundle.loadDir`。 + * + * @method preloadDir + * @param {string} dir - path of the target folder.The path is relative to the bundle folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be preloaded if this argument is supplied. + * @param {Function} [onProgress] - Callback invoked when progression change. + * @param {Number} onProgress.finish - The number of the items that are already completed. + * @param {Number} onProgress.total - The total number of the items. + * @param {Object} onProgress.item - The latest request item + * @param {Function} [onComplete] - A callback which is called when all assets have been loaded, or an error occurs. + * @param {Error} onComplete.error - If one of the asset failed, the complete callback is immediately called with the error. If all assets are preloaded successfully, error will be null. + * @param {RequestItem[]} onComplete.items - An array of all preloaded items. + * + * @example + * // preload all audios (resources/audios/) + * cc.resources.preloadDir('audios', cc.AudioClip); + * + * // preload all textures in "resources/imgs/" + * cc.resources.preloadDir('imgs', cc.Texture2D); + * // wait for while + * cc.resources.loadDir('imgs', cc.Texture2D, (err, textures) => {}); + * + * // preload all prefabs (${project}/assets/bundle1/misc/characters/) from bundle1 folder + * bundle1.preloadDir('misc/characters', cc.Prefab); + * + * // preload all sprite frame (${project}/assets/some/xxx/bundle2/skills/) from bundle2 folder + * bundle2.preloadDir('skills', cc.SpriteFrame); + * // wait for while + * bundle2.loadDir('skills', cc.SpriteFrame, (err, spriteFrames) => {}); + * + * @typescript + * preloadDir(dir: string, type: typeof cc.Asset, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, items: RequestItem[]) => void): void + * preloadDir(dir: string, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, items: RequestItem[]) => void): void + * preloadDir(dir: string, type: typeof cc.Asset, onComplete: (error: Error, items: RequestItem[]) => void): void + * preloadDir(dir: string, type: typeof cc.Asset): void + * preloadDir(dir: string, onComplete: (error: Error, items: RequestItem[]) => void): void + * preloadDir(dir: string): void + */ + preloadDir (dir, type, onProgress, onComplete) { + var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete); + cc.assetManager.preloadAny(dir, { __requestType__: RequestType.DIR, type: type, bundle: this.name }, onProgress, onComplete); + }, + + /** + * !#en + * Loads the scene within this bundle by its name. + * + * !#zh + * 通过场景名称加载分包中的场景。 + * + * @method loadScene + * @param {String} sceneName - The name of the scene to load. + * @param {Object} [options] - Some optional parameters + * @param {Function} [onProgress] - Callback invoked when progression change. + * @param {Number} onProgress.finish - The number of the items that are already completed. + * @param {Number} onProgress.total - The total number of the items. + * @param {Object} onProgress.item - The latest request item + * @param {Function} [onComplete] - callback, will be called after scene launched. + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {SceneAsset} onComplete.sceneAsset - The scene asset + * + * @example + * bundle1.loadScene('first', (err, sceneAsset) => cc.director.runScene(sceneAsset)); + * + * @typescript + * loadScene(sceneName: string, options: Record, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, sceneAsset: cc.SceneAsset) => void): void + * loadScene(sceneName: string, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error, sceneAsset: cc.SceneAsset) => void): void + * loadScene(sceneName: string, options: Record, onComplete: (error: Error, sceneAsset: cc.SceneAsset) => void): void + * loadScene(sceneName: string, onComplete: (error: Error, sceneAsset: cc.SceneAsset) => void): void + * loadScene(sceneName: string, options: Record): void + * loadScene(sceneName: string): void + */ + loadScene (sceneName, options, onProgress, onComplete) { + var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete); + + options.preset = options.preset || 'scene'; + options.bundle = this.name; + cc.assetManager.loadAny({ 'scene': sceneName }, options, onProgress, function (err, sceneAsset) { + if (err) { + cc.error(err.message, err.stack); + onComplete && onComplete(err); + } + else if (sceneAsset instanceof cc.SceneAsset) { + var scene = sceneAsset.scene; + scene._id = sceneAsset._uuid; + scene._name = sceneAsset._name; + onComplete && onComplete(null, sceneAsset); + } + else { + onComplete && onComplete(new Error('The asset ' + sceneAsset._uuid + ' is not a scene')); + } + }); + }, + + /** + * !#en + * Preloads the scene within this bundle by its name. After calling this method, you still need to finish loading by calling `Bundle.loadScene` or `cc.director.loadScene`. + * It will be totally fine to call `Bundle.loadDir` at any time even if the preloading is not yet finished + * + * !#zh + * 通过场景名称预加载分包中的场景.调用完后,你仍然需要通过 `Bundle.loadScene` 或 `cc.director.loadScene` 来完成加载。 + * 就算预加载还没完成,你也可以直接调用 `Bundle.loadScene` 或 `cc.director.loadScene`。 + * + * @method preloadScene + * @param {String} sceneName - The name of the scene to preload. + * @param {Object} [options] - Some optional parameters + * @param {Function} [onProgress] - callback, will be called when the load progression change. + * @param {Number} onProgress.finish - The number of the items that are already completed + * @param {Number} onProgress.total - The total number of the items + * @param {RequestItem} onProgress.item The latest request item + * @param {Function} [onComplete] - callback, will be called after scene loaded. + * @param {Error} onComplete.error - null or the error object. + * + * @example + * bundle1.preloadScene('first'); + * // wait for a while + * bundle1.loadScene('first', (err, scene) => cc.director.runScene(scene)); + * + * @typescript + * preloadScene(sceneName: string, options: Record, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error) => void): void + * preloadScene(sceneName: string, onProgress: (finish: number, total: number, item: RequestItem) => void, onComplete: (error: Error) => void): void + * preloadScene(sceneName: string, options: Record, onComplete: (error: Error) => void): void + * preloadScene(sceneName: string, onComplete: (error: Error) => void): void + * preloadScene(sceneName: string, options: Record): void + * preloadScene(sceneName: string): void + */ + preloadScene (sceneName, options, onProgress, onComplete) { + var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete); + + options.bundle = this.name; + cc.assetManager.preloadAny({'scene': sceneName}, options, onProgress, function (err) { + if (err) { + cc.errorID(1210, sceneName, err.message); + } + onComplete && onComplete(err); + }); + }, + + /** + * !#en + * Get asset within this bundle by path and type.
+ * After you load asset with {{#crossLink "Bundle/load:method"}}{{/crossLink}} or {{#crossLink "Bundle/loadDir:method"}}{{/crossLink}}, + * you can acquire them by passing the path to this API. + * + * !#zh + * 通过路径与类型获取资源。在你使用 {{#crossLink "Bundle/load:method"}}{{/crossLink}} 或者 {{#crossLink "Bundle/loadDir:method"}}{{/crossLink}} 之后, + * 你能通过传路径通过这个 API 获取到这些资源。 + * + * @method get + * @param {String} path - The path of asset + * @param {Function} [type] - Only asset of type will be returned if this argument is supplied. + * @returns {Asset} + * + * @example + * bundle1.get('music/hit', cc.AudioClip); + * + * @typescript + * get (path: string, type?: typeof cc.Asset): T + */ + get (path, type) { + var info = this.getInfoWithPath(path, type); + return assets.get(info && info.uuid); + }, + + /** + * !#en + * Release the asset loaded by {{#crossLink "Bundle/load:method"}}{{/crossLink}} or {{#crossLink "Bundle/loadDir:method"}}{{/crossLink}} and it's dependencies. + * Refer to {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} for detailed informations. + * + * !#zh + * 释放通过 {{#crossLink "Bundle/load:method"}}{{/crossLink}} 或者 {{#crossLink "Bundle/loadDir:method"}}{{/crossLink}} 加载的资源。详细信息请参考 {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} + * + * @method release + * @param {String} path - The path of asset + * @param {Function} [type] - Only asset of type will be released if this argument is supplied. + * + * @example + * // release a texture which is no longer need + * bundle1.release('misc/character/cocos'); + * + * @typescript + * release(path: string, type: typeof cc.Asset): void + * release(path: string): void + */ + release (path, type) { + releaseManager.tryRelease(this.get(path, type), true); + }, + + /** + * !#en + * Release all unused assets within this bundle. Refer to {{#crossLink "AssetManager/releaseAll:method"}}{{/crossLink}} for detailed informations. + * + * !#zh + * 释放此包中的所有没有用到的资源。详细信息请参考 {{#crossLink "AssetManager/releaseAll:method"}}{{/crossLink}} + * + * @method releaseUnusedAssets + * @private + * + * @example + * // release all unused asset within bundle1 + * bundle1.releaseUnusedAssets(); + * + * @typescript + * releaseUnusedAssets(): void + */ + releaseUnusedAssets () { + var self = this; + assets.forEach(function (asset) { + let info = self.getAssetInfo(asset._uuid); + if (info && !info.redirect) { + releaseManager.tryRelease(asset); + } + }); + }, + + /** + * !#en + * Release all assets within this bundle. Refer to {{#crossLink "AssetManager/releaseAll:method"}}{{/crossLink}} for detailed informations. + * + * !#zh + * 释放此包中的所有资源。详细信息请参考 {{#crossLink "AssetManager/releaseAll:method"}}{{/crossLink}} + * + * @method releaseAll + * + * @example + * // release all asset within bundle1 + * bundle1.releaseAll(); + * + * @typescript + * releaseAll(): void + */ + releaseAll () { + var self = this; + assets.forEach(function (asset) { + let info = self.getAssetInfo(asset._uuid); + if (info && !info.redirect) { + releaseManager.tryRelease(asset, true); + } + }); + }, + + _destroy () { + this._config.destroy(); + } + +}; + +module.exports = Bundle; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/cache-manager.ts b/cocos2d/core/asset-manager/cache-manager.ts new file mode 100644 index 00000000000..c1bb9876cb4 --- /dev/null +++ b/cocos2d/core/asset-manager/cache-manager.ts @@ -0,0 +1,154 @@ +import Cache from './cache'; +/** + * @module cc.AssetManager + */ + +/** + * !#en + * Cache manager is a module which controls all caches downloaded from server in non-web platform, it is a singleton + * All member can be accessed with `cc.assetManager.cacheManager`. + * + * !#zh + * 缓存管理器是一个模块,在非 WEB 平台上,用于管理所有从服务器上下载下来的缓存,这是一个单例,所有成员能通过 `cc.assetManager.cacheManager` 访问。 + * + * @class CacheManager + */ +export abstract class CacheManager { + /** + * !#en + * The name of cacheDir + * + * !#zh + * 缓存目录的名称 + * + * @property cacheDir + * @type {String} + * @default 'gamecaches' + */ + public abstract cacheDir: String; + + /** + * !#en + * Whether or not cache asset into user's storage space, this property only works on mini-game platforms + * + * !#zh + * 是否缓存资源到用户存储空间,此属性只在小游戏平台有效 + * + * @property cacheEnabled + * @type {Boolean} + * @default true + */ + public abstract cacheEnabled: Boolean; + + /** + * !#en + * Whether or not auto clear cache when storage ran out, this property only works on mini-game platforms + * + * !#zh + * 是否在存储空间满了后自动清理缓存,此属性只在小游戏平台有效 + * + * @property autoClear + * @type {Boolean} + * @default true + */ + public abstract autoClear: Boolean; + + /** + * !#en + * The interval between caching resources, this property only works on mini-game platforms, unit: ms + * + * !#zh + * 缓存资源的间隔时间,此属性只在小游戏平台有效,单位:ms + * + * @property cacheInterval + * @type {Number} + * @default 500 + */ + public abstract cacheInterval: Number; + + /** + * !#en + * The interval between deleting resources, when you use `cleanLRU`, the resources will be deleted as this interval, unit: ms + * + * !#zh + * 清理资源的间隔时间,当你使用 `cleanLRU` 时,资源将以此间隔被删除,单位:ms + * + * @property deleteInterval + * @type {Number} + * @default 500 + */ + public abstract deleteInterval: Number; + + /** + * !#en + * List of all cached files + * + * !#zh + * 所有缓存文件列表 + * + * @property cachedFiles + * @type {Cache} + * @typescript + * cachedFiles: Cache<{ bundle: string, url: string, lastTime: number }> + */ + public abstract cachedFiles: Cache; + + /** + * !#en + * Get cached path with origin url + * + * !#zh + * 通过原始 url 获取缓存后的路径 + * + * @method getCache + * @param {string} originUrl + * @returns {String} The cached path + */ + public abstract getCache (originUrl: string): string; + + /** + * !#en + * Get temporary path with origin url, this method only works on mini-game platforms + * + * !#zh + * 通过原始 url 获取临时文件的路径,此方法只在小游戏平台有效 + * + * @method getTemp + * @param {string} originUrl + * @returns {String} The temp path + */ + public abstract getTemp (originUrl: string): string; + + /** + * !#en + * Clear all caches, please use with caution, If necessary, we recommend using it before the game is launched + * + * !#zh + * 清空所有缓存,请谨慎使用,如果必要的话,我们建议在游戏启动之前使用 + * + * @method clearCache + */ + public abstract clearCache (): void; + + /** + * !#en + * Clear part of caches with LRU strategy + * + * !#zh + * 使用 LRU 策略清空部分缓存 + * + * @method clearLRU + */ + public abstract clearLRU (): void; + + /** + * !#en + * Remove cache with origin url + * + * !#zh + * 通过原始 url 移除缓存 + * + * @method removeCache + */ + public abstract removeCache (originUrl: string): void; +} diff --git a/cocos2d/core/asset-manager/cache.js b/cocos2d/core/asset-manager/cache.js new file mode 100644 index 00000000000..858594ef0b5 --- /dev/null +++ b/cocos2d/core/asset-manager/cache.js @@ -0,0 +1,267 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +/** + * @module cc.AssetManager + */ + +const js = require('../platform/js'); +/** + * !#en + * use to cache something + * + * !#zh + * 用于缓存某些内容 + * + * @class Cache + * @typescript Cache + */ +function Cache (map) { + if (map) { + this._map = map; + this._count = Object.keys(map).length; + } + else { + this._map = js.createMap(true); + this._count = 0; + } +} + +Cache.prototype = { + + /** + * !#en + * Create a cache + * + * !#zh + * 创建一个 cache + * + * @method constructor + * @param {Object} [map] - An object used to initialize + * + * @typescript + * constructor(map?: Record) + */ + constructor: Cache, + + /** + * !#en + * Add Key-Value to cache + * + * !#zh + * 增加键值对到缓存中 + * + * @method add + * @param {String} key - The key + * @param {*} val - The value + * @returns {*} The value + * + * @example + * var cache = new Cache(); + * cache.add('test', null); + * + * @typescript + * add(key: string, val: T): T + */ + add (key, val) { + if (!(key in this._map)) this._count++; + return this._map[key] = val; + }, + + /** + * !#en + * Get the cached content by key + * + * !#zh + * 通过 key 获取对应的 value + * + * @method get + * @param {string} key - The key + * @returns {*} The corresponding content + * + * @example + * var cache = new Cache(); + * var test = cache.get('test'); + * + * @typescript + * get(key: string): T + */ + get (key) { + return this._map[key]; + }, + + /** + * !#en + * Check whether or not content exists by key + * + * !#zh + * 通过 Key 判断是否存在对应的内容 + * + * @method has + * @param {string} key - The key + * @returns {boolean} True indecates that content of the key exists + * + * @example + * var cache = new Cache(); + * var exist = cache.has('test'); + * + * @typescript + * has(key: string): boolean + */ + has (key) { + return key in this._map; + }, + + /** + * !#en + * Remove the cached content by key + * + * !#zh + * 通过 Key 移除对应的内容 + * + * @method remove + * @param {string} key - The key + * @returns {*} The removed content + * + * @example + * var cache = new Cache(); + * var content = cache.remove('test'); + * + * @typescript + * remove(key: string): T + */ + remove (key) { + var out = this._map[key]; + if (key in this._map) { + delete this._map[key]; + this._count--; + } + return out; + }, + + /** + * !#en + * Clear all content + * + * !#zh + * 清除所有内容 + * + * @method clear + * + * @example + * var cache = new Cache(); + * cache.clear(); + * + * @typescript + * clear():void + */ + clear () { + if (this._count !== 0) { + this._map = js.createMap(true); + this._count = 0; + } + }, + + /** + * !#en + * Enumerate all content and invoke function + * + * !#zh + * 枚举所有内容并执行方法 + * + * @method forEach + * @param {Function} func - Function to be invoked + * @param {*} func.val - The value + * @param {String} func.key - The corresponding key + * + * @example + * var cache = new Cache(); + * cache.forEach((val, key) => console.log(key)); + * + * @typescript + * forEach(func: (val: T, key: string) => void): void + */ + forEach (func) { + for (var key in this._map) { + func(this._map[key], key); + } + }, + + /** + * !#en + * Enumerate all content to find one element which can fulfill condition + * + * !#zh + * 枚举所有内容,找到一个可以满足条件的元素 + * + * @method find + * @param {Function} predicate - The condition + * @returns {*} content + * + * @example + * var cache = new Cache(); + * var val = cache.find((val, key) => key === 'test'); + * + * @typescript + * find(predicate: (val: T, key: string) => boolean): T + */ + find (predicate) { + for (var key in this._map) { + if (predicate(this._map[key], key)) return this._map[key]; + } + return null; + }, + + /** + * !#en + * The count of cached content + * + * !#zh + * 缓存数量 + * + * @property count + * @type {Number} + */ + get count () { + return this._count; + }, + + /** + * !#en + * Destroy this cache + * + * !#zh + * 销毁这个 cache + * + * @method destroy + * + * @typescript + * destroy(): void + */ + destroy () { + this._map = null; + } +}; + +module.exports = Cache; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/config.js b/cocos2d/core/asset-manager/config.js new file mode 100644 index 00000000000..01ba231681b --- /dev/null +++ b/cocos2d/core/asset-manager/config.js @@ -0,0 +1,257 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const js = require('../platform/js'); +const Cache = require('./cache'); +const { normalize } = require('./helper'); +const { processOptions } = require('./utilities'); + +function Config () { + + this.name = ''; + + this.base = ''; + + this.importBase = ''; + + this.nativeBase = ''; + + this.deps = null; + + this.assetInfos = new Cache(); + + this.scenes = new Cache(); + + this.paths = new Cache(); +} + +Config.prototype = { + + constructor: Config, + + init: function (options) { + processOptions(options); + + this.importBase = options.importBase || ''; + this.nativeBase = options.nativeBase || ''; + this.base = options.base || ''; + this.name = options.name || ''; + this.deps = options.deps || []; + // init + this._initUuid(options.uuids); + this._initPath(options.paths); + this._initScene(options.scenes); + this._initPackage(options.packs); + this._initVersion(options.versions); + this._initRedirect(options.redirect); + }, + + _initUuid: function (uuidList) { + if (!uuidList) return; + this.assetInfos.clear(); + for (var i = 0, l = uuidList.length; i < l; i++) { + var uuid = uuidList[i]; + this.assetInfos.add(uuid, {uuid}); + } + }, + + _initPath: function (pathList) { + if (!pathList) return; + var paths = this.paths; + paths.clear(); + for (var uuid in pathList) { + var info = pathList[uuid]; + var path = info[0]; + var type = info[1]; + var isSubAsset = info.length === 3; + + var assetInfo = this.assetInfos.get(uuid); + assetInfo.path = path; + assetInfo.ctor = js._getClassById(type); + if (paths.has(path)) { + if (isSubAsset) { + paths.get(path).push(assetInfo); + } + else { + paths.get(path).splice(0, 0, assetInfo); + } + } + else { + paths.add(path, [assetInfo]); + } + } + }, + + _initScene: function (sceneList) { + if (!sceneList) return; + var scenes = this.scenes; + scenes.clear(); + var assetInfos = this.assetInfos; + for (var sceneName in sceneList) { + var uuid = sceneList[sceneName]; + var assetInfo = assetInfos.get(uuid); + assetInfo.url = sceneName; + scenes.add(sceneName, assetInfo); + } + }, + + _initPackage: function (packageList) { + if (!packageList) return; + var assetInfos = this.assetInfos; + for (var packUuid in packageList) { + var uuids = packageList[packUuid]; + var pack = {uuid: packUuid, packs: uuids, ext:'.json'}; + assetInfos.add(packUuid, pack); + + for (var i = 0, l = uuids.length; i < l; i++) { + var uuid = uuids[i]; + var assetInfo = assetInfos.get(uuid); + var assetPacks = assetInfo.packs; + if (assetPacks) { + if (l === 1) { + assetPacks.splice(0, 0, pack); + } + else { + assetPacks.push(pack); + } + } + else { + assetInfo.packs = [pack]; + } + } + } + }, + + _initVersion: function (versions) { + if (!versions) return; + var assetInfos = this.assetInfos; + var entries = versions.import; + if (entries) { + for (var i = 0, l = entries.length; i < l; i += 2) { + var uuid = entries[i]; + var assetInfo = assetInfos.get(uuid); + assetInfo.ver = entries[i + 1]; + } + } + entries = versions.native; + if (entries) { + for (var i = 0, l = entries.length; i < l; i += 2) { + var uuid = entries[i]; + var assetInfo = assetInfos.get(uuid); + assetInfo.nativeVer = entries[i + 1]; + } + } + }, + + _initRedirect: function (redirect) { + if (!redirect) return; + var assetInfos = this.assetInfos; + for (var i = 0, l = redirect.length; i < l; i += 2) { + var uuid = redirect[i]; + var assetInfo = assetInfos.get(uuid); + assetInfo.redirect = redirect[i + 1]; + } + }, + + getInfoWithPath: function (path, type) { + + if (!path) { + return null; + } + path = normalize(path); + var items = this.paths.get(path); + if (items) { + if (type) { + for (var i = 0, l = items.length; i < l; i++) { + var assetInfo = items[i]; + if (js.isChildClassOf(assetInfo.ctor, type)) { + return assetInfo; + } + } + } + else { + return items[0]; + } + } + return null; + }, + + getDirWithPath: function (path, type, out) { + path = normalize(path); + if (path[path.length - 1] === '/') { + path = path.slice(0, -1); + } + + var infos = out || []; + function isMatchByWord (path, test) { + if (path.length > test.length) { + var nextAscii = path.charCodeAt(test.length); + return nextAscii === 47; // '/' + } + return true; + } + this.paths.forEach(function (items, p) { + if ((p.startsWith(path) && isMatchByWord(p, path)) || !path) { + for (var i = 0, l = items.length; i < l; i++) { + var entry = items[i]; + if (!type || js.isChildClassOf(entry.ctor, type)) { + infos.push(entry); + } + } + } + }); + + return infos; + }, + + getAssetInfo: function (uuid) { + return this.assetInfos.get(uuid); + }, + + getSceneInfo: function (name) { + if (!name.endsWith('.fire')) { + name += '.fire'; + } + if (name[0] !== '/' && !name.startsWith('db://')) { + name = '/' + name; // 使用全名匹配 + } + // search scene + var info = this.scenes.find(function (val, key) { + return key.endsWith(name); + }); + return info; + }, + + destroy: function () { + this.paths.destroy(); + this.scenes.destroy(); + this.assetInfos.destroy(); + } +}; + +if (CC_TEST) { + cc._Test.Config = Config; +} + +module.exports = Config; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/depend-util.js b/cocos2d/core/asset-manager/depend-util.js new file mode 100644 index 00000000000..cf1971ad640 --- /dev/null +++ b/cocos2d/core/asset-manager/depend-util.js @@ -0,0 +1,246 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const Cache = require('./cache'); +const deserialize = require('./deserialize'); +const { files, parsed } = require('./shared'); +import { hasNativeDep , getDependUuidList } from '../platform/deserialize-compiled'; +import deserializeForCompiled from '../platform/deserialize-compiled'; + +/** + * @module cc.AssetManager + */ +/** + * !#en + * Control asset's dependency list, it is a singleton. All member can be accessed with `cc.assetManager.dependUtil` + * + * !#zh + * 控制资源的依赖列表,这是一个单例, 所有成员能通过 `cc.assetManager.dependUtil` 访问 + * + * @class DependUtil + */ +var dependUtil = { + _depends: new Cache(), + + init () { + this._depends.clear(); + }, + + /** + * !#en + * Get asset's native dependency. For example, Texture's native dependency is image. + * + * !#zh + * 获取资源的原生依赖,例如 Texture 的原生依赖是图片 + * + * @method getNativeDep + * @param {string} uuid - asset's uuid + * @returns {Object} native dependency + * + * @example + * var dep = dependUtil.getNativeDep('fcmR3XADNLgJ1ByKhqcC5Z'); + * + * @typescript + * getNativeDep(uuid: string): Record + */ + getNativeDep (uuid) { + let depend = this._depends.get(uuid); + if (depend) return depend.nativeDep && Object.assign({}, depend.nativeDep); + return null; + }, + + /** + * !#en + * Get asset's direct referencing non-native dependency list. For example, Material's non-native dependencies are Texture. + * + * !#zh + * 获取资源直接引用的非原生依赖列表,例如,材质的非原生依赖是 Texture + * + * @method getDeps + * @param {string} uuid - asset's uuid + * @returns {string[]} direct referencing non-native dependency list + * + * @example + * var deps = dependUtil.getDeps('fcmR3XADNLgJ1ByKhqcC5Z'); + * + * @typescript + * getDeps(uuid: string): string[] + */ + getDeps (uuid) { + if (this._depends.has(uuid)) { + return this._depends.get(uuid).deps; + } + return []; + }, + + /** + * !#en + * Get non-native dependency list of the loaded asset, include indirect reference. + * The returned array stores the dependencies with their uuid, after retrieve dependencies, + * + * !#zh + * 获取某个已经加载好的资源的所有非原生依赖资源列表,包括间接引用的资源,并保存在数组中返回。 + * 返回的数组将仅保存依赖资源的 uuid。 + * + * @method getDependsRecursively + * @param {String} uuid - The asset's uuid + * @returns {string[]} non-native dependency list + * + * @example + * var deps = dependUtil.getDepsRecursively('fcmR3XADNLgJ1ByKhqcC5Z'); + * + * @typescript + * getDepsRecursively(uuid: string): string[] + */ + getDepsRecursively (uuid) { + var exclude = Object.create(null), depends = []; + this._descend(uuid, exclude, depends); + return depends; + }, + + _descend (uuid, exclude, depends) { + var deps = this.getDeps(uuid); + for (var i = 0; i < deps.length; i++) { + var depend = deps[i]; + if ( !exclude[depend] ) { + exclude[depend] = true; + depends.push(depend); + this._descend(depend, exclude, depends); + } + } + }, + + remove (uuid) { + this._depends.remove(uuid); + }, + + /** + * !#en + * Extract dependency list from serialized data or asset and then store in cache. + * + * !#zh + * 从序列化数据或资源中提取出依赖列表,并且存储在缓存中。 + * + * @param {string} uuid - The uuid of serialized data or asset + * @param {Object} json - Serialized data or asset + * @returns {Object} dependency list, include non-native and native dependency + * + * @example + * downloader.downloadFile('test.json', {responseType: 'json'}, null, (err, file) => { + * var dependencies = parse('fcmR3XADNLgJ1ByKhqcC5Z', file); + * }); + * + * @typescript + * parse(uuid: string, json: any): { deps?: string[], nativeDep?: any } + */ + parse (uuid, json) { + var out = null; + if (Array.isArray(json) || json.__type__) { + + if (out = this._depends.get(uuid)) return out; + + if (Array.isArray(json) && (!(CC_BUILD || deserializeForCompiled.isCompiledJson(json)) || !hasNativeDep(json))) { + out = { + deps: this._parseDepsFromJson(json), + }; + } + else { + try { + var asset = deserialize(json); + out = this._parseDepsFromAsset(asset) + out.nativeDep && (out.nativeDep.uuid = uuid); + parsed.add(uuid + '@import', asset); + } + catch (e) { + files.remove(uuid + '@import'); + out = { deps: [] }; + } + } + } + // get deps from an existing asset + else { + if (!CC_EDITOR && (out = this._depends.get(uuid)) && out.parsedFromExistAsset) return out; + out = this._parseDepsFromAsset(json); + } + // cache dependency list + this._depends.add(uuid, out); + return out; + }, + + _parseDepsFromAsset: function (asset) { + var out = { + deps: [], + parsedFromExistAsset: true, + preventPreloadNativeObject: asset.constructor.preventPreloadNativeObject, + preventDeferredLoadDependents: asset.constructor.preventDeferredLoadDependents + }; + let deps = asset.__depends__; + for (var i = 0, l = deps.length; i < l; i++) { + var dep = deps[i].uuid; + out.deps.push(dep); + } + + if (asset.__nativeDepend__) { + out.nativeDep = asset._nativeDep; + } + + return out; + }, + + _parseDepsFromJson: CC_EDITOR || CC_PREVIEW ? function (json) { + + if (deserializeForCompiled.isCompiledJson(json)) { + let depends = getDependUuidList(json); + depends.forEach((uuid, index) => depends[index] = cc.assetManager.utils.decodeUuid(uuid)); + return depends; + } + + var depends = []; + function parseDependRecursively (data, out) { + if (!data || typeof data !== 'object' || data.__id__) return; + var uuid = data.__uuid__; + if (Array.isArray(data)) { + for (let i = 0, l = data.length; i < l; i++) { + parseDependRecursively(data[i], out); + } + } + else if (uuid) { + out.push(cc.assetManager.utils.decodeUuid(uuid)); + } + else { + for (var prop in data) { + parseDependRecursively(data[prop], out); + } + } + } + parseDependRecursively(json, depends); + return depends; + } : function (json) { + let depends = getDependUuidList(json); + depends.forEach((uuid, index) => depends[index] = cc.assetManager.utils.decodeUuid(uuid)); + return depends; + } +}; + +module.exports = dependUtil; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/deprecated.js b/cocos2d/core/asset-manager/deprecated.js new file mode 100644 index 00000000000..0f6e17d6539 --- /dev/null +++ b/cocos2d/core/asset-manager/deprecated.js @@ -0,0 +1,779 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +const js = require('../platform/js'); +require('../CCDirector'); +const utilities = require('./utilities'); +const dependUtil = require('./depend-util'); +const releaseManager = require('./releaseManager'); +const downloader = require('./downloader'); +const factory = require('./factory'); + +const ImageFmts = ['.png', '.jpg', '.bmp', '.jpeg', '.gif', '.ico', '.tiff', '.webp', '.image', '.pvr', '.pkm']; +const AudioFmts = ['.mp3', '.ogg', '.wav', '.m4a']; + +function GetTrue () { return true; } + +const md5Pipe = { + transformURL (url) { + url = url.replace(/.*[/\\][0-9a-fA-F]{2}[/\\]([0-9a-fA-F-]{8,})/, function (match, uuid) { + var bundle = cc.assetManager.bundles.find(function (bundle) { + return bundle.getAssetInfo(uuid); + }); + let hashValue = ''; + if (bundle) { + var info = bundle.getAssetInfo(uuid); + if (url.startsWith(bundle.base + bundle._config.nativeBase)) { + hashValue = info.nativeVer; + } + else { + hashValue = info.ver; + } + } + return hashValue ? match + '.' + hashValue : match; + }); + return url + }, +}; + +/** + * `cc.loader` is deprecated, please backup your project and upgrade to {{#crossLink "AssetManager"}}{{/crossLink}} + * + * @class loader + * @static + * @deprecated cc.loader is deprecated, please backup your project and upgrade to cc.assetManager + */ +const loader = { + /** + * `cc.loader.onProgress` is deprecated, please transfer onProgress to API as a parameter + * @property onProgress + * @deprecated cc.loader.onProgress is deprecated, please transfer onProgress to API as a parameter + */ + onProgress: null, + _autoReleaseSetting: Object.create(null), + + get _cache () { + return cc.assetManager.assets._map; + }, + + /** + * `cc.loader.load` is deprecated, please use {{#crossLink "AssetManager/loadAny:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.load is deprecated, please use cc.assetManager.loadAny instead + * + * @method load + * @param {String|String[]|Object} resources - Url list in an array + * @param {Function} [progressCallback] - Callback invoked when progression change + * @param {Number} progressCallback.completedCount - The number of the items that are already completed + * @param {Number} progressCallback.totalCount - The total number of the items + * @param {Object} progressCallback.item - The latest item which flow out the pipeline + * @param {Function} [completeCallback] - Callback invoked when all resources loaded + * @typescript + * load(resources: string|string[]|{uuid?: string, url?: string, type?: string}, completeCallback?: Function): void + * load(resources: string|string[]|{uuid?: string, url?: string, type?: string}, progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: Function|null): void + */ + load (resources, progressCallback, completeCallback) { + if (completeCallback === undefined) { + if (progressCallback !== undefined) { + completeCallback = progressCallback; + progressCallback = null; + } + } + resources = Array.isArray(resources) ? resources : [resources]; + for (var i = 0; i < resources.length; i++) { + var item = resources[i]; + if (typeof item === 'string') { + resources[i] = { url: item, __isNative__: true}; + } + else { + if (item.type) { + item.ext = '.' + item.type; + item.type = undefined; + } + + if (item.url) { + item.__isNative__ = true; + } + } + } + var images = []; + var audios = []; + cc.assetManager.loadAny(resources, null, (finish, total, item) => { + if (item.content) { + if (ImageFmts.includes(item.ext)) { + images.push(item.content); + } + else if (AudioFmts.includes(item.ext)) { + audios.push(item.content); + } + } + progressCallback && progressCallback(finish, total, item); + }, (err, native) => { + var res = null; + if (!err) { + native = Array.isArray(native) ? native : [native]; + for (var i = 0; i < native.length; i++) { + var item = native[i]; + if (!(item instanceof cc.Asset)) { + var asset = item; + var url = resources[i].url; + if (images.includes(asset)) { + factory.create(url, item, '.png', null, (err, image) => { + asset = native[i] = image; + }); + } + else if (audios.includes(asset)) { + factory.create(url, item, '.mp3', null, (err, audio) => { + asset = native[i] = audio; + }); + } + cc.assetManager.assets.add(url, asset); + } + } + if (native.length > 1) { + var map = Object.create(null); + native.forEach(function (asset) { + map[asset._uuid] = asset; + }); + res = { isCompleted: GetTrue, _map: map }; + } + else { + res = native[0]; + } + } + completeCallback && completeCallback(err, res); + }); + }, + + /** + * `cc.loader.getXMLHttpRequest` is deprecated, please use `XMLHttpRequest` directly + * + * @method getXMLHttpRequest + * @deprecated cc.loader.getXMLHttpRequest is deprecated, please use XMLHttpRequest directly + * @returns {XMLHttpRequest} + */ + getXMLHttpRequest () { + return new XMLHttpRequest(); + }, + + _parseLoadResArgs: utilities.parseLoadResArgs, + + /** + * `cc.loader.getItem` is deprecated, please use `cc.assetManager.asset.get` instead + * + * @method getItem + * @param {Object} id The id of the item + * @return {Object} + * @deprecated cc.loader.getItem is deprecated, please use cc.assetManager.assets.get instead + */ + getItem (key) { + return cc.assetManager.assets.has(key) ? { content: cc.assetManager.assets.get(key) } : null; + }, + + /** + * `cc.loader.loadRes` is deprecated, please use {{#crossLink "Bundle/load:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.loadRes is deprecated, please use cc.resources.load instead + * @method loadRes + * @param {String} url - Url of the target resource. + * The url is relative to the "resources" folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be loaded if this argument is supplied. + * @param {Function} [progressCallback] - Callback invoked when progression change. + * @param {Number} progressCallback.completedCount - The number of the items that are already completed. + * @param {Number} progressCallback.totalCount - The total number of the items. + * @param {Object} progressCallback.item - The latest item which flow out the pipeline. + * @param {Function} [completeCallback] - Callback invoked when the resource loaded. + * @param {Error} completeCallback.error - The error info or null if loaded successfully. + * @param {Object} completeCallback.resource - The loaded resource if it can be found otherwise returns null. + * + * @typescript + * loadRes(url: string, type: typeof cc.Asset, progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: ((error: Error, resource: any) => void)|null): void + * loadRes(url: string, type: typeof cc.Asset, completeCallback: (error: Error, resource: any) => void): void + * loadRes(url: string, type: typeof cc.Asset): void + * loadRes(url: string, progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: ((error: Error, resource: any) => void)|null): void + * loadRes(url: string, completeCallback: (error: Error, resource: any) => void): void + * loadRes(url: string): void + */ + loadRes (url, type, progressCallback, completeCallback) { + var { type, onProgress, onComplete } = this._parseLoadResArgs(type, progressCallback, completeCallback); + var extname = cc.path.extname(url); + if (extname) { + // strip extname + url = url.slice(0, - extname.length); + } + cc.resources.load(url, type, onProgress, onComplete); + }, + + /** + * `cc.loader.loadResArray` is deprecated, please use {{#crossLink "Bundle/load:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.loadResArray is deprecated, please use cc.resources.load instead + * @method loadResArray + * @param {String[]} urls - Array of URLs of the target resource. + * The url is relative to the "resources" folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be loaded if this argument is supplied. + * @param {Function} [progressCallback] - Callback invoked when progression change. + * @param {Number} progressCallback.completedCount - The number of the items that are already completed. + * @param {Number} progressCallback.totalCount - The total number of the items. + * @param {Object} progressCallback.item - The latest item which flow out the pipeline. + * @param {Function} [completeCallback] - A callback which is called when all assets have been loaded, or an error occurs. + * @param {Error} completeCallback.error - If one of the asset failed, the complete callback is immediately called + * with the error. If all assets are loaded successfully, error will be null. + * @param {Asset[]|Array} completeCallback.assets - An array of all loaded assets. + * If nothing to load, assets will be an empty array. + * @typescript + * loadResArray(url: string[], type: typeof cc.Asset, progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: ((error: Error, resource: any[]) => void)|null): void + * loadResArray(url: string[], type: typeof cc.Asset, completeCallback: (error: Error, resource: any[]) => void): void + * loadResArray(url: string[], type: typeof cc.Asset): void + * loadResArray(url: string[], progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: ((error: Error, resource: any[]) => void)|null): void + * loadResArray(url: string[], completeCallback: (error: Error, resource: any[]) => void): void + * loadResArray(url: string[]): void + * loadResArray(url: string[], type: typeof cc.Asset[]): void + */ + loadResArray (urls, type, progressCallback, completeCallback) { + var { type, onProgress, onComplete } = this._parseLoadResArgs(type, progressCallback, completeCallback); + urls.forEach((url, i) => { + var extname = cc.path.extname(url); + if (extname) { + // strip extname + urls[i] = url.slice(0, - extname.length); + } + }) + cc.resources.load(urls, type, onProgress, onComplete); + }, + + /** + * `cc.loader.loadResDir` is deprecated, please use {{#crossLink "Bundle/loadDir:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.loadResDir is deprecated, please use cc.resources.loadDir instead + * @method loadResDir + * @param {String} url - Url of the target folder. + * The url is relative to the "resources" folder, extensions must be omitted. + * @param {Function} [type] - Only asset of type will be loaded if this argument is supplied. + * @param {Function} [progressCallback] - Callback invoked when progression change. + * @param {Number} progressCallback.completedCount - The number of the items that are already completed. + * @param {Number} progressCallback.totalCount - The total number of the items. + * @param {Object} progressCallback.item - The latest item which flow out the pipeline. + * @param {Function} [completeCallback] - A callback which is called when all assets have been loaded, or an error occurs. + * @param {Error} completeCallback.error - If one of the asset failed, the complete callback is immediately called + * with the error. If all assets are loaded successfully, error will be null. + * @param {Asset[]|Array} completeCallback.assets - An array of all loaded assets. + * If nothing to load, assets will be an empty array. + * @param {String[]} completeCallback.urls - An array that lists all the URLs of loaded assets. + * + * @typescript + * loadResDir(url: string, type: typeof cc.Asset, progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: ((error: Error, resource: any[], urls: string[]) => void)|null): void + * loadResDir(url: string, type: typeof cc.Asset, completeCallback: (error: Error, resource: any[], urls: string[]) => void): void + * loadResDir(url: string, type: typeof cc.Asset): void + * loadResDir(url: string, progressCallback: (completedCount: number, totalCount: number, item: any) => void, completeCallback: ((error: Error, resource: any[], urls: string[]) => void)|null): void + * loadResDir(url: string, completeCallback: (error: Error, resource: any[], urls: string[]) => void): void + * loadResDir(url: string): void + */ + loadResDir (url, type, progressCallback, completeCallback) { + var { type, onProgress, onComplete } = this._parseLoadResArgs(type, progressCallback, completeCallback); + cc.resources.loadDir(url, type, onProgress, function (err, assets) { + var urls = []; + if (!err) { + var infos = cc.resources.getDirWithPath(url, type); + urls = infos.map(function (info) { + return info.path; + }); + } + onComplete && onComplete(err, assets, urls); + }); + }, + + /** + * `cc.loader.getRes` is deprecated, please use {{#crossLink "Bundle/get:method"}}{{/crossLink}} instead + * + * @method getRes + * @param {String} url + * @param {Function} [type] - Only asset of type will be returned if this argument is supplied. + * @returns {*} + * @deprecated cc.loader.getRes is deprecated, please use cc.resources.get instead + */ + getRes (url, type) { + return cc.assetManager.assets.has(url) ? cc.assetManager.assets.get(url) : cc.resources.get(url, type); + }, + + getResCount () { + return cc.assetManager.assets.count; + }, + + /** + * `cc.loader.getDependsRecursively` is deprecated, please use use {{#crossLink "DependUtil/getDepsRecursively:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.getDependsRecursively is deprecated, please use use cc.assetManager.dependUtil.getDepsRecursively instead + * @method getDependsRecursively + * @param {Asset|String} owner - The owner asset or the resource url or the asset's uuid + * @returns {Array} + */ + getDependsRecursively (owner) { + if (!owner) return []; + return dependUtil.getDepsRecursively(typeof owner === 'string' ? owner : owner._uuid).concat([ owner._uuid ]); + }, + + /** + * `cc.loader.assetLoader` was removed, assetLoader and md5Pipe were merged into {{#crossLink "AssetManager/transformPipeline:property"}}{{/crossLink}} + * + * @property assetLoader + * @deprecated cc.loader.assetLoader was removed, assetLoader and md5Pipe were merged into cc.assetManager.transformPipeline + * @type {Object} + */ + get assetLoader () { + if (CC_DEBUG) { + cc.error('cc.loader.assetLoader was removed, assetLoader and md5Pipe were merged into cc.assetManager.transformPipeline'); + } + }, + + /** + * `cc.loader.md5Pipe` is deprecated, assetLoader and md5Pipe were merged into {{#crossLink "AssetManager/transformPipeline:property"}}{{/crossLink}} + * + * @property md5Pipe + * @deprecated cc.loader.md5Pipe is deprecated, assetLoader and md5Pipe were merged into cc.assetManager.transformPipeline + * @type {Object} + */ + get md5Pipe () { + return md5Pipe; + }, + + /** + * `cc.loader.downloader` is deprecated, please use {{#crossLink "AssetManager/downloader:property"}}{{/crossLink}} instead + * + * @deprecated cc.loader.downloader is deprecated, please use cc.assetManager.downloader instead + * @property downloader + * @type {Object} + */ + get downloader () { + return cc.assetManager.downloader; + }, + + /** + * `cc.loader.loader` is deprecated, please use {{#crossLink "AssetManager/parser:property"}}{{/crossLink}} instead + * + * @property loader + * @type {Object} + * @deprecated cc.loader.loader is deprecated, please use cc.assetManager.parser instead + */ + get loader () { + return cc.assetManager.parser; + }, + + /** + * `cc.loader.addDownloadHandlers` is deprecated, please use `cc.assetManager.downloader.register` instead + * + * @method addDownloadHandlers + * @param {Object} extMap Custom supported types with corresponded handler + * @deprecated cc.loader.addDownloadHandlers is deprecated, please use cc.assetManager.downloader.register instead + */ + addDownloadHandlers (extMap) { + if (CC_DEBUG) { + cc.warn('`cc.loader.addDownloadHandlers` is deprecated, please use `cc.assetManager.downloader.register` instead'); + } + var handler = Object.create(null); + for (var type in extMap) { + var func = extMap[type]; + handler['.' + type] = function (url, options, onComplete) { + func({url}, onComplete); + }; + } + cc.assetManager.downloader.register(handler); + }, + + /** + * `cc.loader.addLoadHandlers` is deprecated, please use `cc.assetManager.parser.register` instead + * + * @method addLoadHandlers + * @param {Object} extMap Custom supported types with corresponded handler + * @deprecated cc.loader.addLoadHandlers is deprecated, please use cc.assetManager.parser.register instead + */ + addLoadHandlers (extMap) { + if (CC_DEBUG) { + cc.warn('`cc.loader.addLoadHandlers` is deprecated, please use `cc.assetManager.parser.register` instead'); + } + var handler = Object.create(null); + for (var type in extMap) { + var func = extMap[type]; + handler['.' + type] = function (file, options, onComplete) { + func({content: file}, onComplete); + }; + } + cc.assetManager.parser.register(handler); + }, + + flowInDeps () { + if (CC_DEBUG) { + cc.error('cc.loader.flowInDeps was removed'); + } + }, + + /** + * `cc.loader.release` is deprecated, please use {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} instead + * + * @method release + * @param {Asset|String|Array} asset + * @deprecated cc.loader.release is deprecated, please use cc.assetManager.releaseAsset instead + */ + release (asset) { + if (Array.isArray(asset)) { + for (let i = 0; i < asset.length; i++) { + var key = asset[i]; + if (typeof key === 'string') key = cc.assetManager.assets.get(key); + let isBuiltin = cc.assetManager.builtins._assets.find(function (assets) { + return assets.find(builtinAsset => builtinAsset === key); + }); + if (isBuiltin) continue; + cc.assetManager.releaseAsset(key); + } + } + else if (asset) { + if (typeof asset === 'string') asset = cc.assetManager.assets.get(asset); + let isBuiltin = cc.assetManager.builtins._assets.find(function (assets) { + return assets.find(builtinAsset => builtinAsset === asset); + }); + if (isBuiltin) return; + cc.assetManager.releaseAsset(asset); + } + }, + + /** + * `cc.loader.releaseAsset` is deprecated, please use {{#crossLink "AssetManager/releaseAsset:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.releaseAsset is deprecated, please use cc.assetManager.releaseAsset instead + * @method releaseAsset + * @param {Asset} asset + */ + releaseAsset (asset) { + cc.assetManager.releaseAsset(asset); + }, + + /** + * `cc.loader.releaseRes` is deprecated, please use {{#crossLink "AssetManager/releaseRes:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.releaseRes is deprecated, please use cc.assetManager.releaseRes instead + * @method releaseRes + * @param {String} url + * @param {Function} [type] - Only asset of type will be released if this argument is supplied. + */ + releaseRes (url, type) { + cc.resources.release(url, type); + }, + + /** + * `cc.loader.releaseResDir` was removed, please use {{#crossLink "AssetManager/releaseRes:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.releaseResDir was removed, please use cc.assetManager.releaseRes instead + * @method releaseResDir + */ + releaseResDir () { + if (CC_DEBUG) { + cc.error('cc.loader.releaseResDir was removed, please use cc.assetManager.releaseAsset instead'); + } + }, + + /** + * `cc.loader.releaseAll` is deprecated, please use {{#crossLink "AssetManager/releaseAll:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.releaseAll is deprecated, please use cc.assetManager.releaseAll instead + * @method releaseAll + */ + releaseAll () { + cc.assetManager.releaseAll(); + cc.assetManager.assets.clear(); + }, + + /** + * `cc.loader.removeItem` is deprecated, please use `cc.assetManager.assets.remove` instead + * + * @deprecated cc.loader.removeItem is deprecated, please use cc.assetManager.assets.remove instead + * @method removeItem + * @param {Object} id The id of the item + * @return {Boolean} succeed or not + */ + removeItem (key) { + cc.assetManager.assets.remove(key); + }, + + /** + * `cc.loader.setAutoRelease` is deprecated, if you want to prevent some asset from auto releasing, please use {{#crossLink "Asset/addRef:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.setAutoRelease is deprecated, if you want to prevent some asset from auto releasing, please use cc.Asset.addRef instead + * @method setAutoRelease + * @param {Asset|String} assetOrUrlOrUuid - asset object or the raw asset's url or uuid + * @param {Boolean} autoRelease - indicates whether should release automatically + */ + setAutoRelease (asset, autoRelease) { + if (typeof asset === 'object') asset = asset._uuid; + this._autoReleaseSetting[asset] = !!autoRelease; + }, + + /** + * `cc.loader.setAutoReleaseRecursively` is deprecated, if you want to prevent some asset from auto releasing, please use {{#crossLink "Asset/addRef:method"}}{{/crossLink}} instead + * + * @method setAutoReleaseRecursively + * @param {Asset|String} assetOrUrlOrUuid - asset object or the raw asset's url or uuid + * @param {Boolean} autoRelease - indicates whether should release automatically + * @deprecated cc.loader.setAutoReleaseRecursively is deprecated, if you want to prevent some asset from auto releasing, please use cc.Asset.addRef instead + */ + setAutoReleaseRecursively (asset, autoRelease) { + if (typeof asset === 'object') asset = asset._uuid; + autoRelease = !!autoRelease; + this._autoReleaseSetting[asset] = autoRelease; + var depends = dependUtil.getDepsRecursively(asset); + for (var i = 0; i < depends.length; i++) { + var depend = depends[i]; + this._autoReleaseSetting[depend] = autoRelease; + } + }, + + /** + * `cc.loader.isAutoRelease` is deprecated + * + * @method isAutoRelease + * @param {Asset|String} assetOrUrl - asset object or the raw asset's url + * @returns {Boolean} + * @deprecated cc.loader.isAutoRelease is deprecated + */ + isAutoRelease (asset) { + if (typeof asset === 'object') asset = asset._uuid; + return !!this._autoReleaseSetting[asset]; + } +}; + +/** + * @class Downloader + */ +/** + * `cc.loader.downloader.loadSubpackage` is deprecated, please use {{#crossLink "AssetManager/loadBundle:method"}}{{/crossLink}} instead + * + * @deprecated cc.loader.downloader.loadSubpackage is deprecated, please use AssetManager.loadBundle instead + * @method loadSubpackage + * @param {String} name - Subpackage name + * @param {Function} [completeCallback] - Callback invoked when subpackage loaded + * @param {Error} completeCallback.error - error information + */ +downloader.loadSubpackage = function (name, completeCallback) { + cc.assetManager.loadBundle(name, null, completeCallback); +}; + +/** + * @deprecated cc.AssetLibrary is deprecated, please backup your project and upgrade to cc.assetManager + */ +var AssetLibrary = { + + /** + * @deprecated cc.AssetLibrary.init is deprecated, please use cc.assetManager.init instead + */ + init (options) { + options.importBase = options.libraryPath; + options.nativeBase = CC_BUILD ? options.rawAssetsBase : options.libraryPath; + cc.assetManager.init(options); + if (options.rawAssets) { + var resources = new cc.AssetManager.Bundle(); + resources.init({ + name: cc.AssetManager.BuiltinBundleName.RESOURCES, + importBase: options.importBase, + nativeBase: options.nativeBase, + paths: options.rawAssets.assets, + uuids: Object.keys(options.rawAssets.assets), + }); + } + }, + + /** + * @deprecated cc.AssetLibrary is deprecated, please use cc.assetManager.loadAny instead + */ + loadAsset (uuid, onComplete) { + cc.assetManager.loadAny(uuid, onComplete); + }, + + getLibUrlNoExt () { + if (CC_DEBUG) { + cc.error('cc.AssetLibrary.getLibUrlNoExt was removed, if you want to transform url, please use cc.assetManager.helper.getUrlWithUuid instead'); + } + }, + + queryAssetInfo () { + if (CC_DEBUG) { + cc.error('cc.AssetLibrary.queryAssetInfo was removed, only available in the editor by using cc.assetManager.editorExtend.queryAssetInfo'); + } + } +}; + +/** + * `cc.url` is deprecated + * + * @deprecated cc.url is deprecated + * @class url + * @static + */ +cc.url = { + normalize (url) { + cc.warnID(1400, 'cc.url.normalize', 'cc.assetManager.utils.normalize'); + return cc.assetManager.utils.normalize(url); + }, + + /** + * `cc.url.raw` is deprecated, please use `cc.resources.load` directly, or use `Asset.nativeUrl` instead. + * + * @deprecated cc.url.raw is deprecated, please use cc.resources.load directly, or use Asset.nativeUrl instead. + * @method raw + * @param {String} url + * @return {String} + */ + raw (url) { + cc.warnID(1400, 'cc.url.raw', 'cc.resources.load'); + if (url.startsWith('resources/')) { + return cc.assetManager._transform({'path': cc.path.changeExtname(url.substr(10)), bundle: cc.AssetManager.BuiltinBundleName.RESOURCES, __isNative__: true, ext: cc.path.extname(url)}); + } + return ''; + } +}; + +let onceWarns = { + loader: true, + assetLibrary: true, +}; + +Object.defineProperties(cc, { + loader: { + get () { + if (CC_DEBUG) { + if (onceWarns.loader) { + onceWarns.loader = false; + cc.log('cc.loader is deprecated, use cc.assetManager instead please. See https://docs.cocos.com/creator/manual/zh/release-notes/asset-manager-upgrade-guide.html'); + } + } + return loader; + } + }, + + AssetLibrary: { + get () { + if (CC_DEBUG) { + if (onceWarns.assetLibrary) { + onceWarns.assetLibrary = false; + cc.log('cc.AssetLibrary is deprecated, use cc.assetManager instead please. See https://docs.cocos.com/creator/manual/zh/release-notes/asset-manager-upgrade-guide.html'); + } + } + return AssetLibrary; + } + }, + + /** + * `cc.LoadingItems` was removed, please use {{#crossLink "Task"}}{{/crossLink}} instead + * + * @deprecated cc.LoadingItems was removed, please use cc.AssetManager.Task instead + * @class LoadingItems + */ + LoadingItems: { + get () { + cc.warnID(1400, 'cc.LoadingItems', 'cc.AssetManager.Task'); + return cc.AssetManager.Task; + } + }, + + Pipeline: { + get () { + cc.warnID(1400, 'cc.Pipeline', 'cc.AssetManager.Pipeline'); + return cc.AssetManager.Pipeline; + } + } +}); + +js.obsolete(cc, 'cc.RawAsset', 'cc.Asset'); + +/** + * @class Asset + */ +/** + * `cc.Asset.url` is deprecated, please use {{#crossLink "Asset/nativeUrl:property"}}{{/crossLink}} instead + * @property url + * @type {String} + * @deprecated cc.Asset.url is deprecated, please use cc.Asset.nativeUrl instead + */ +js.obsolete(cc.Asset.prototype, 'cc.Asset.url', 'nativeUrl'); + +/** + * @class macro + * @static + */ +Object.defineProperties(cc.macro, { + /** + * `cc.macro.DOWNLOAD_MAX_CONCURRENT` is deprecated now, please use {{#crossLink "Downloader/maxConcurrency:property"}}{{/crossLink}} instead + * + * @property DOWNLOAD_MAX_CONCURRENT + * @type {Number} + * @deprecated cc.macro.DOWNLOAD_MAX_CONCURRENT is deprecated now, please use cc.assetManager.downloader.maxConcurrency instead + */ + DOWNLOAD_MAX_CONCURRENT: { + get () { + return cc.assetManager.downloader.maxConcurrency; + }, + + set (val) { + cc.assetManager.downloader.maxConcurrency = val; + } + } +}); + +Object.assign(cc.director, { + _getSceneUuid (sceneName) { + cc.assetManager.main.getSceneInfo(sceneName); + } +}); + +Object.defineProperties(cc.game, { + _sceneInfos: { + get () { + var scenes = []; + cc.assetManager.main._config.scenes.forEach(function (val) { + scenes.push(val); + }); + return scenes; + } + } +}); + +var parseParameters = utilities.parseParameters; +utilities.parseParameters = function (options, onProgress, onComplete) { + var result = parseParameters(options, onProgress, onComplete); + result.onProgress = result.onProgress || loader.onProgress; + return result; +}; + +var autoRelease = releaseManager._autoRelease; +releaseManager._autoRelease = function () { + autoRelease.apply(this, arguments); + var releaseSettings = loader._autoReleaseSetting; + var keys = Object.keys(releaseSettings); + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + if (releaseSettings[key] === true) { + var asset = cc.assetManager.assets.get(key); + asset && releaseManager.tryRelease(asset); + } + } +}; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/deserialize.js b/cocos2d/core/asset-manager/deserialize.js new file mode 100644 index 00000000000..d20916f122e --- /dev/null +++ b/cocos2d/core/asset-manager/deserialize.js @@ -0,0 +1,103 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +const helper = require('./helper'); +const MissingClass = CC_EDITOR && Editor.require('app://editor/page/scene-utils/missing-class-reporter').MissingClass; +require('../platform/deserialize'); + +function deserialize (json, options) { + var classFinder, missingClass; + if (CC_EDITOR) { + missingClass = MissingClass; + classFinder = function (type, data, owner, propName) { + var res = missingClass.classFinder(type, data, owner, propName); + if (res) { + return res; + } + return cc._MissingScript; + }; + classFinder.onDereferenced = missingClass.classFinder.onDereferenced; + } + else { + classFinder = cc._MissingScript.safeFindClass; + } + + let pool = null; + if (!CC_PREVIEW) { + pool = cc.deserialize.Details.pool; + } + else { + let { default: deserializeForCompiled } = require('../platform/deserialize-compiled'); + let deserializeForEditor = require('../platform/deserialize-editor'); + if (deserializeForCompiled.isCompiledJson(json)) { + pool = deserializeForCompiled.Details.pool; + } + else { + pool = deserializeForEditor.Details.pool; + } + } + var tdInfo = pool.get(); + + var asset; + try { + asset = cc.deserialize(json, tdInfo, { + classFinder: classFinder, + customEnv: options + }); + } + catch (e) { + pool.put(tdInfo); + throw e; + } + + if (CC_EDITOR && missingClass) { + missingClass.reportMissingClass(asset); + missingClass.reset(); + } + + var uuidList = tdInfo.uuidList; + var objList = tdInfo.uuidObjList; + var propList = tdInfo.uuidPropList; + var depends = []; + + for (var i = 0; i < uuidList.length; i++) { + var dependUuid = uuidList[i]; + depends[i] = { + uuid: helper.decodeUuid(dependUuid), + owner: objList[i], + prop: propList[i] + }; + } + + // non-native deps + asset.__depends__ = depends; + // native dep + asset._native && (asset.__nativeDepend__ = true); + pool.put(tdInfo); + return asset; + +} + +module.exports = deserialize; diff --git a/cocos2d/core/load-pipeline/audio-downloader.js b/cocos2d/core/asset-manager/download-dom-audio.js similarity index 52% rename from cocos2d/core/load-pipeline/audio-downloader.js rename to cocos2d/core/asset-manager/download-dom-audio.js index 6245ad10bb2..eeed4a66754 100644 --- a/cocos2d/core/load-pipeline/audio-downloader.js +++ b/cocos2d/core/asset-manager/download-dom-audio.js @@ -1,8 +1,7 @@ /**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -23,22 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ +var __audioSupport = cc.sys.__audioSupport; +const { parseParameters } = require('./utilities'); -const sys = require('../platform/CCSys'); -const debug = require('../CCDebug'); +function downloadDomAudio (url, options, onComplete) { + var { options, onComplete } = parseParameters(options, undefined, onComplete); -var __audioSupport = sys.__audioSupport; -var formatSupport = __audioSupport.format; -var context = __audioSupport.context; - -function loadDomAudio (item, callback) { var dom = document.createElement('audio'); - dom.src = item.url; - - if (CC_WECHATGAME) { - callback(null, dom); - return; - } + dom.src = url; var clearEvent = function () { clearTimeout(timer); @@ -47,74 +38,31 @@ function loadDomAudio (item, callback) { if(__audioSupport.USE_LOADER_EVENT) dom.removeEventListener(__audioSupport.USE_LOADER_EVENT, success, false); }; + var timer = setTimeout(function () { if (dom.readyState === 0) failure(); else success(); }, 8000); + var success = function () { clearEvent(); - callback(null, dom); + onComplete && onComplete(null, dom); }; + var failure = function () { clearEvent(); - var message = 'load audio failure - ' + item.url; + var message = 'load audio failure - ' + url; cc.log(message); - callback(message); + onComplete && onComplete(new Error(message)); }; + dom.addEventListener("canplaythrough", success, false); dom.addEventListener("error", failure, false); if(__audioSupport.USE_LOADER_EVENT) dom.addEventListener(__audioSupport.USE_LOADER_EVENT, success, false); + return dom; } -function loadWebAudio (item, callback) { - if (!context) - callback(new Error(debug.getError(4926))); - - var request = cc.loader.getXMLHttpRequest(); - request.open("GET", item.url, true); - request.responseType = "arraybuffer"; - - // Our asynchronous callback - request.onload = function () { - context["decodeAudioData"](request.response, function(buffer){ - //success - callback(null, buffer); - }, function(){ - //error - callback('decode error - ' + item.id, null); - }); - }; - - request.onerror = function(){ - callback('request error - ' + item.id, null); - }; - - request.send(); -} - -function downloadAudio (item, callback) { - if (formatSupport.length === 0) { - return new Error(debug.getError(4927)); - } - - var loader; - if (!__audioSupport.WEB_AUDIO) { - // If WebAudio is not supported, load using DOM mode - loader = loadDomAudio; - } - else { - var loadByDeserializedAudio = item._owner instanceof cc.AudioClip; - if (loadByDeserializedAudio) { - loader = (item._owner.loadMode === cc.AudioClip.LoadMode.WEB_AUDIO) ? loadWebAudio : loadDomAudio; - } - else { - loader = (item.urlParam && item.urlParam['useDom']) ? loadDomAudio : loadWebAudio; - } - } - loader(item, callback); -} - -module.exports = downloadAudio; +module.exports = downloadDomAudio; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/download-dom-image.js b/cocos2d/core/asset-manager/download-dom-image.js new file mode 100644 index 00000000000..a80f60ffcc0 --- /dev/null +++ b/cocos2d/core/asset-manager/download-dom-image.js @@ -0,0 +1,56 @@ + +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +const { parseParameters } = require('./utilities'); + +function downloadDomImage (url, options, onComplete) { + var { options, onComplete } = parseParameters(options, undefined, onComplete); + + var img = new Image(); + + if (window.location.protocol !== 'file:') { + img.crossOrigin = 'anonymous'; + } + + function loadCallback () { + img.removeEventListener('load', loadCallback); + img.removeEventListener('error', errorCallback); + onComplete && onComplete(null, img); + } + + function errorCallback () { + img.removeEventListener('load', loadCallback); + img.removeEventListener('error', errorCallback); + onComplete && onComplete(new Error(cc.debug.getError(4930, url))); + } + + img.addEventListener('load', loadCallback); + img.addEventListener('error', errorCallback); + img.src = url; + return img; +} + +module.exports = downloadDomImage; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/download-file.js b/cocos2d/core/asset-manager/download-file.js new file mode 100644 index 00000000000..6867ecd453f --- /dev/null +++ b/cocos2d/core/asset-manager/download-file.js @@ -0,0 +1,79 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const { parseParameters } = require('./utilities'); + +function downloadFile (url, options, onProgress, onComplete) { + var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete); + + var xhr = new XMLHttpRequest(), errInfo = 'download failed: ' + url + ', status: '; + + xhr.open('GET', url, true); + + if (options.responseType !== undefined) xhr.responseType = options.responseType; + if (options.withCredentials !== undefined) xhr.withCredentials = options.withCredentials; + if (options.mimeType !== undefined && xhr.overrideMimeType ) xhr.overrideMimeType(options.mimeType); + if (options.timeout !== undefined) xhr.timeout = options.timeout; + + if (options.header) { + for (var header in options.header) { + xhr.setRequestHeader(header, options.header[header]); + } + } + + xhr.onload = function () { + if ( xhr.status === 200 || xhr.status === 0 ) { + onComplete && onComplete(null, xhr.response); + } else { + onComplete && onComplete(new Error(errInfo + xhr.status + '(no response)')); + } + + }; + + if (onProgress) { + xhr.onprogress = function (e) { + if (e.lengthComputable) { + onProgress(e.loaded, e.total); + } + }; + } + + xhr.onerror = function(){ + onComplete && onComplete(new Error(errInfo + xhr.status + '(error)')); + }; + + xhr.ontimeout = function(){ + onComplete && onComplete(new Error(errInfo + xhr.status + '(time out)')); + }; + + xhr.onabort = function(){ + onComplete && onComplete(new Error(errInfo + xhr.status + '(abort)')); + }; + + xhr.send(null); + + return xhr; +} + +module.exports = downloadFile; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/download-script.js b/cocos2d/core/asset-manager/download-script.js new file mode 100644 index 00000000000..07230ce28b5 --- /dev/null +++ b/cocos2d/core/asset-manager/download-script.js @@ -0,0 +1,66 @@ + +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const { parseParameters } = require('./utilities'); + +const downloaded = {}; + +function downloadScript (url, options, onComplete) { + var { options, onComplete } = parseParameters(options, undefined, onComplete); + + // no need to load script again + if (downloaded[url]) { + return onComplete && onComplete(null); + } + + var d = document, s = document.createElement('script'); + + if (window.location.protocol !== 'file:') { + s.crossOrigin = 'anonymous'; + } + + s.async = options.async; + s.src = url; + function loadHandler () { + s.parentNode.removeChild(s); + s.removeEventListener('load', loadHandler, false); + s.removeEventListener('error', errorHandler, false); + downloaded[url] = true; + onComplete && onComplete(null); + } + + function errorHandler() { + s.parentNode.removeChild(s); + s.removeEventListener('load', loadHandler, false); + s.removeEventListener('error', errorHandler, false); + onComplete && onComplete(new Error(cc.debug.getError(4928, url))); + } + + s.addEventListener('load', loadHandler, false); + s.addEventListener('error', errorHandler, false); + d.body.appendChild(s); +} + +module.exports = downloadScript; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/downloader.js b/cocos2d/core/asset-manager/downloader.js new file mode 100644 index 00000000000..d0e5da05376 --- /dev/null +++ b/cocos2d/core/asset-manager/downloader.js @@ -0,0 +1,598 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +/** + * @module cc.AssetManager + */ +const js = require('../platform/js'); +const debug = require('../CCDebug'); +const { loadFont } = require('./font-loader'); +const callInNextTick = require('../platform/utils').callInNextTick; +const downloadDomImage = require('./download-dom-image'); +const downloadDomAudio = require('./download-dom-audio'); +const downloadFile = require('./download-file'); +const downloadScript = require('./download-script.js'); +const Cache = require('./cache'); +const { files } = require('./shared'); +const { __audioSupport, capabilities } = require('../platform/CCSys'); +const { urlAppendTimestamp, retry } = require('./utilities'); + +const REGEX = /^\w+:\/\/.*/; + + +var formatSupport = __audioSupport.format || []; + +var unsupported = function (url, options, onComplete) { + onComplete(new Error(debug.getError(4927))); +} + +var downloadAudio = function (url, options, onComplete) { + // web audio need to download file as arrayBuffer + if (options.audioLoadMode !== cc.AudioClip.LoadMode.DOM_AUDIO) { + downloadArrayBuffer(url, options, onComplete); + } + else { + downloadDomAudio(url, options, onComplete); + } +}; + +var downloadAudio = (!CC_EDITOR || !Editor.isMainProcess) ? (formatSupport.length === 0 ? unsupported : (__audioSupport.WEB_AUDIO ? downloadAudio : downloadDomAudio)) : null; + +var downloadImage = function (url, options, onComplete) { + // if createImageBitmap is valid, we can transform blob to ImageBitmap. Otherwise, just use HTMLImageElement to load + var func = capabilities.imageBitmap && cc.macro.ALLOW_IMAGE_BITMAP ? downloadBlob : downloadDomImage; + func.apply(this, arguments); +}; + +var downloadBlob = function (url, options, onComplete) { + options.responseType = "blob"; + downloadFile(url, options, options.onFileProgress, onComplete); +}; + +var downloadJson = function (url, options, onComplete) { + options.responseType = "json"; + downloadFile(url, options, options.onFileProgress, function (err, data) { + if (!err && typeof data === 'string') { + try { + data = JSON.parse(data); + } + catch (e) { + err = e; + } + } + onComplete && onComplete(err, data); + }); +}; + +var downloadArrayBuffer = function (url, options, onComplete) { + options.responseType = "arraybuffer"; + downloadFile(url, options, options.onFileProgress, onComplete); +}; + +var downloadText = function (url, options, onComplete) { + options.responseType = "text"; + downloadFile(url, options, options.onFileProgress, onComplete); +}; + +var downloadVideo = function (url, options, onComplete) { + onComplete(null, url); +}; + +var downloadBundle = function (nameOrUrl, options, onComplete) { + let bundleName = cc.path.basename(nameOrUrl); + let url = nameOrUrl; + if (!REGEX.test(url)) url = 'assets/' + bundleName; + var version = options.version || downloader.bundleVers[bundleName]; + var count = 0; + var config = `${url}/config.${version ? version + '.' : ''}json`; + let out = null, error = null; + downloadJson(config, options, function (err, response) { + if (err) { + error = err; + } + out = response; + out && (out.base = url + '/'); + count++; + if (count === 2) { + onComplete(error, out); + } + }); + + var js = `${url}/index.${version ? version + '.' : ''}js`; + downloadScript(js, options, function (err) { + if (err) { + error = err; + } + count++; + if (count === 2) { + onComplete(error, out); + } + }); +}; + +var _downloading = new Cache(); +var _queue = []; +var _queueDirty = false; + +// the number of loading thread +var _totalNum = 0; + +// the number of request that launched in this period +var _totalNumThisPeriod = 0; + +// last time, if now - lastTime > period, refresh _totalNumThisPeriod. +var _lastDate = -1; + +// if _totalNumThisPeriod equals max, move request to next period using setTimeOut. +var _checkNextPeriod = false; + +var updateTime = function () { + var now = Date.now(); + // use deltaTime as period + if (now - _lastDate > cc.director._deltaTime * 1000) { + _totalNumThisPeriod = 0; + _lastDate = now; + } +}; + +// handle the rest request in next period +var handleQueue = function (maxConcurrency, maxRequestsPerFrame) { + _checkNextPeriod = false; + updateTime(); + while (_queue.length > 0 && _totalNum < maxConcurrency && _totalNumThisPeriod < maxRequestsPerFrame) { + if (_queueDirty) { + _queue.sort(function (a, b) { + return a.priority - b.priority; + }); + _queueDirty = false; + } + var nextOne = _queue.pop(); + if (!nextOne) { + break; + } + _totalNum++; + _totalNumThisPeriod++; + nextOne.invoke(); + } + + if (_queue.length > 0 && _totalNum < maxConcurrency) { + callInNextTick(handleQueue, maxConcurrency, maxRequestsPerFrame); + _checkNextPeriod = true; + } +} + + +/** + * !#en + * Control all download process, it is a singleton. All member can be accessed with `cc.assetManager.downloader` , it can download several types of files: + * 1. Text + * 2. Image + * 3. Audio + * 4. Assets + * 5. Scripts + * + * !#zh + * 管理所有下载过程,downloader 是个单例,所有成员能通过 `cc.assetManager.downloader` 访问,它能下载以下几种类型的文件: + * 1. 文本 + * 2. 图片 + * 3. 音频 + * 4. 资源 + * 5. 脚本 + * + * @class Downloader + */ +var downloader = { + + _remoteServerAddress: '', + + /** + * !#en + * The address of remote server + * + * !#zh + * 远程服务器地址 + * + * @property remoteServerAddress + * @type {string} + * @default '' + */ + get remoteServerAddress () { + return this._remoteServerAddress; + }, + + /** + * !#en + * The maximum number of concurrent when downloading + * + * !#zh + * 下载时的最大并发数 + * + * @property maxConcurrency + * @type {number} + * @default 6 + */ + maxConcurrency: 6, + + /** + * !#en + * The maximum number of request can be launched per frame when downloading + * + * !#zh + * 下载时每帧可以启动的最大请求数 + * + * @property maxRequestsPerFrame + * @type {number} + * @default 6 + */ + maxRequestsPerFrame: 6, + + /** + * !#en + * The max number of retries when fail + * + * !#zh + * 失败重试次数 + * + * @property maxRetryCount + * @type {Number} + */ + maxRetryCount: 3, + + appendTimeStamp: false, + + limited: true, + + /** + * !#en + * Wait for while before another retry, unit: ms + * + * !#zh + * 重试的间隔时间 + * + * @property retryInterval + * @type {Number} + */ + retryInterval: 2000, + + bundleVers: null, + + /* + * !#en + * Use Image element to download image + * + * !#zh + * 使用 Image 元素来下载图片 + * + * @method downloadDomImage + * @param {string} url - Url of the image + * @param {Object} [options] - Some optional paramters + * @param {Function} [onComplete] - Callback when image loaded or failed + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {HTMLImageElement} onComplete.img - The loaded Image element, null if error occurred + * @returns {HTMLImageElement} The image element + * + * @example + * downloadDomImage('http://example.com/test.jpg', null, (err, img) => console.log(err)); + * + * @typescript + * downloadDomImage(url: string, options?: Record , onComplete?: (err: Error, img: HTMLImageElement) => void): HTMLImageElement + * downloadDomImage(url: string, onComplete?: (err: Error, img: HTMLImageElement) => void): HTMLImageElement + */ + downloadDomImage: downloadDomImage, + + /* + * !#en + * Use audio element to download audio + * + * !#zh + * 使用 Audio 元素来下载音频 + * + * @method downloadDomAudio + * @param {string} url - Url of the audio + * @param {Object} [options] - Some optional paramters + * @param {Function} [onComplete] - Callback invoked when audio loaded or failed + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {HTMLAudioElement} onComplete.audio - The loaded audio element, null if error occurred + * @returns {HTMLAudioElement} The audio element + * + * @example + * downloadDomAudio('http://example.com/test.mp3', null, (err, audio) => console.log(err)); + * + * @typescript + * downloadDomAudio(url: string, options?: Record, onComplete?: (err: Error, audio: HTMLAudioElement) => void): HTMLAudioElement + * downloadDomAudio(url: string, onComplete?: (err: Error, audio: HTMLAudioElement) => void): HTMLAudioElement + */ + downloadDomAudio: downloadDomAudio, + + /* + * !#en + * Use XMLHttpRequest to download file + * + * !#zh + * 使用 XMLHttpRequest 来下载文件 + * + * @method downloadFile + * @param {string} url - Url of the file + * @param {Object} [options] - Some optional paramters + * @param {string} [options.responseType] - Indicate which type of content should be returned + * @param {boolean} [options.withCredentials] - Indicate whether or not cross-site Access-Contorl requests should be made using credentials + * @param {string} [options.mimeType] - Indicate which type of content should be returned. In some browsers, responseType does't work, you can use mimeType instead + * @param {Number} [options.timeout] - Represent the number of ms a request can take before being terminated. + * @param {Object} [options.header] - The header should be tranferred to server + * @param {Function} [onFileProgress] - Callback continuously during download is processing + * @param {Number} onFileProgress.loaded - Size of downloaded content. + * @param {Number} onFileProgress.total - Total size of content. + * @param {Function} [onComplete] - Callback when file loaded or failed + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {*} onComplete.response - The loaded content, null if error occurred, type of content can be indicated by options.responseType + * @returns {XMLHttpRequest} The xhr to be send + * + * @example + * downloadFile('http://example.com/test.bin', {responseType: 'arraybuffer'}, null, (err, arrayBuffer) => console.log(err)); + * + * @typescript + * downloadFile(url: string, options?: Record, onFileProgress?: (loaded: Number, total: Number) => void, onComplete?: (err: Error, response: any) => void): XMLHttpRequest + * downloadFile(url: string, onFileProgress?: (loaded: Number, total: Number) => void, onComplete?: (err: Error, response: any) => void): XMLHttpRequest + * downloadFile(url: string, options?: Record, onComplete?: (err: Error, response: any) => void): XMLHttpRequest + * downloadFile(url: string, onComplete?: (err: Error, response: any) => void): XMLHttpRequest + */ + downloadFile: downloadFile, + + /* + * !#en + * Load script + * + * !#zh + * 加载脚本 + * + * @method downloadScript + * @param {string} url - Url of the script + * @param {Object} [options] - Some optional paramters + * @param {boolean} [options.isAsync] - Indicate whether or not loading process should be async + * @param {Function} [onComplete] - Callback when script loaded or failed + * @param {Error} onComplete.err - The occurred error, null indicetes success + * + * @example + * downloadScript('http://localhost:8080/index.js', null, (err) => console.log(err)); + * + * @typescript + * downloadScript(url: string, options?: Record, onComplete?: (err: Error) => void): void; + * downloadScript(url: string, onComplete?: (err: Error) => void): void; + */ + downloadScript: downloadScript, + + init (bundleVers, remoteServerAddress) { + _downloading.clear(); + _queue.length = 0; + this._remoteServerAddress = remoteServerAddress || ''; + this.bundleVers = bundleVers || Object.create(null); + }, + + /** + * !#en + * Register custom handler if you want to change default behavior or extend downloader to download other format file + * + * !#zh + * 当你想修改默认行为或者拓展 downloader 来下载其他格式文件时可以注册自定义的 handler + * + * @method register + * @param {string|Object} type - Extension likes '.jpg' or map likes {'.jpg': jpgHandler, '.png': pngHandler} + * @param {Function} [handler] - handler + * @param {string} handler.url - url + * @param {Object} handler.options - some optional paramters will be transferred to handler. + * @param {Function} handler.onComplete - callback when finishing downloading + * + * @example + * downloader.register('.tga', (url, options, onComplete) => onComplete(null, null)); + * downloader.register({'.tga': (url, options, onComplete) => onComplete(null, null), '.ext': (url, options, onComplete) => onComplete(null, null)}); + * + * @typescript + * register(type: string, handler: (url: string, options: Record, onComplete: (err: Error, content: any) => void) => void): void + * register(map: Record, onComplete: (err: Error, content: any) => void) => void>): void + */ + register (type, handler) { + if (typeof type === 'object') { + js.mixin(downloaders, type); + } + else { + downloaders[type] = handler; + } + }, + + /** + * !#en + * Use corresponding handler to download file under limitation + * + * !#zh + * 在限制下使用对应的 handler 来下载文件 + * + * @method download + * @param {string} url - The url should be downloaded + * @param {string} type - The type indicates that which handler should be used to download, such as '.jpg' + * @param {Object} options - some optional paramters will be transferred to the corresponding handler. + * @param {Function} [options.onFileProgress] - progressive callback will be transferred to handler. + * @param {Number} [options.maxRetryCount] - How many times should retry when download failed + * @param {Number} [options.maxConcurrency] - The maximum number of concurrent when downloading + * @param {Number} [options.maxRequestsPerFrame] - The maximum number of request can be launched per frame when downloading + * @param {Number} [options.priority] - The priority of this url, default is 0, the greater number is higher priority. + * @param {Function} onComplete - callback when finishing downloading + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {*} onComplete.contetnt - The downloaded file + * + * @example + * download('http://example.com/test.tga', '.tga', {onFileProgress: (loaded, total) => console.lgo(loaded/total)}, onComplete: (err) => console.log(err)); + * + * @typescript + * download(id: string, url: string, type: string, options: Record, onComplete: (err: Error, content: any) => void): void + */ + download (id, url, type, options, onComplete) { + let func = downloaders[type] || downloaders['default']; + let self = this; + // if it is downloaded, don't download again + let file, downloadCallbacks; + if (file = files.get(id)) { + onComplete(null, file); + } + else if (downloadCallbacks = _downloading.get(id)) { + downloadCallbacks.push(onComplete); + for (let i = 0, l = _queue.length; i < l; i++) { + var item = _queue[i]; + if (item.id === id) { + var priority = options.priority || 0; + if (item.priority < priority) { + item.priority = priority; + _queueDirty = true; + } + return; + } + } + } + else { + // if download fail, should retry + var maxRetryCount = options.maxRetryCount || this.maxRetryCount; + var maxConcurrency = options.maxConcurrency || this.maxConcurrency; + var maxRequestsPerFrame = options.maxRequestsPerFrame || this.maxRequestsPerFrame; + + function process (index, callback) { + if (index === 0) { + _downloading.add(id, [onComplete]); + } + + if (!self.limited) return func(urlAppendTimestamp(url), options, callback); + + // refresh + updateTime(); + + function invoke () { + func(urlAppendTimestamp(url), options, function () { + // when finish downloading, update _totalNum + _totalNum--; + if (!_checkNextPeriod && _queue.length > 0) { + callInNextTick(handleQueue, maxConcurrency, maxRequestsPerFrame); + _checkNextPeriod = true; + } + callback.apply(this, arguments); + }); + } + + if (_totalNum < maxConcurrency && _totalNumThisPeriod < maxRequestsPerFrame) { + invoke(); + _totalNum++; + _totalNumThisPeriod++; + } + else { + // when number of request up to limitation, cache the rest + _queue.push({ id, priority: options.priority || 0, invoke }); + _queueDirty = true; + + if (!_checkNextPeriod && _totalNum < maxConcurrency) { + callInNextTick(handleQueue, maxConcurrency, maxRequestsPerFrame); + _checkNextPeriod = true; + } + } + } + + // when retry finished, invoke callbacks + function finale (err, result) { + if (!err) files.add(id, result); + var callbacks = _downloading.remove(id); + for (let i = 0, l = callbacks.length; i < l; i++) { + callbacks[i](err, result); + } + } + + retry(process, maxRetryCount, this.retryInterval, finale); + } + } +}; + +// dafault handler map +var downloaders = { + // Images + '.png' : downloadImage, + '.jpg' : downloadImage, + '.bmp' : downloadImage, + '.jpeg' : downloadImage, + '.gif' : downloadImage, + '.ico' : downloadImage, + '.tiff' : downloadImage, + '.webp' : downloadImage, + '.image' : downloadImage, + '.pvr': downloadArrayBuffer, + '.pkm': downloadArrayBuffer, + + // Audio + '.mp3' : downloadAudio, + '.ogg' : downloadAudio, + '.wav' : downloadAudio, + '.m4a' : downloadAudio, + + // Txt + '.txt' : downloadText, + '.xml' : downloadText, + '.vsh' : downloadText, + '.fsh' : downloadText, + '.atlas' : downloadText, + + '.tmx' : downloadText, + '.tsx' : downloadText, + + '.json' : downloadJson, + '.ExportJson' : downloadJson, + '.plist' : downloadText, + + '.fnt' : downloadText, + + // font + '.font' : loadFont, + '.eot' : loadFont, + '.ttf' : loadFont, + '.woff' : loadFont, + '.svg' : loadFont, + '.ttc' : loadFont, + + // Video + '.mp4': downloadVideo, + '.avi': downloadVideo, + '.mov': downloadVideo, + '.mpg': downloadVideo, + '.mpeg': downloadVideo, + '.rm': downloadVideo, + '.rmvb': downloadVideo, + + // Binary + '.binary' : downloadArrayBuffer, + '.bin': downloadArrayBuffer, + '.dbbin': downloadArrayBuffer, + '.skel': downloadArrayBuffer, + + '.js': downloadScript, + + 'bundle': downloadBundle, + + 'default': downloadText + +}; + +module.exports = downloader; diff --git a/cocos2d/core/asset-manager/factory.js b/cocos2d/core/asset-manager/factory.js new file mode 100644 index 00000000000..4516d97ea58 --- /dev/null +++ b/cocos2d/core/asset-manager/factory.js @@ -0,0 +1,185 @@ +/**************************************************************************** + Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const Bundle = require('./bundle'); +const Cache = require('./cache'); +const { assets, bundles } = require('./shared'); + +const _creating = new Cache(); + +function createTexture (id, data, options, onComplete) { + let out = null, err = null; + try { + out = new cc.Texture2D(); + out._nativeUrl = id; + out._nativeAsset = data; + } + catch (e) { + err = e; + } + onComplete && onComplete(err, out); +} + +function createAudioClip (id, data, options, onComplete) { + let out = new cc.AudioClip(); + out._nativeUrl = id; + out._nativeAsset = data; + out.duration = data.duration; + onComplete && onComplete(null, out); +} + +function createJsonAsset (id, data, options, onComplete) { + let out = new cc.JsonAsset(); + out.json = data; + onComplete && onComplete(null, out); +} + +function createTextAsset (id, data, options, onComplete) { + let out = new cc.TextAsset(); + out.text = data; + onComplete && onComplete(null, out); +} + +function createFont (id, data, options, onComplete) { + let out = new cc.TTFFont(); + out._nativeUrl = id; + out._nativeAsset = data; + onComplete && onComplete(null, out); +} + +function createBufferAsset (id, data, options, onComplete) { + let out = new cc.BufferAsset(); + out._nativeUrl = id; + out._nativeAsset = data; + onComplete && onComplete(null, out); +} + +function createAsset (id, data, options, onComplete) { + let out = new cc.Asset(); + out._nativeUrl = id; + out._nativeAsset = data; + onComplete && onComplete(null, out); +} + +function createBundle (id, data, options, onComplete) { + let bundle = bundles.get(data.name); + if (!bundle) { + bundle = new Bundle(); + data.base = data.base || id + '/'; + bundle.init(data); + } + onComplete && onComplete(null, bundle); +} + +const factory = { + + register (type, handler) { + if (typeof type === 'object') { + cc.js.mixin(producers, type); + } + else { + producers[type] = handler; + } + }, + + create (id, data, type, options, onComplete) { + var func = producers[type] || producers['default']; + let asset, creating; + if (asset = assets.get(id)) { + onComplete(null, asset); + } + else if (creating = _creating.get(id)) { + creating.push(onComplete); + } + else { + _creating.add(id, [onComplete]); + func(id, data, options, function (err, data) { + if (!err && data instanceof cc.Asset) { + data._uuid = id; + assets.add(id, data); + } + let callbacks = _creating.remove(id); + for (let i = 0, l = callbacks.length; i < l; i++) { + callbacks[i](err, data); + } + }); + } + } +}; + +const producers = { + // Images + '.png' : createTexture, + '.jpg' : createTexture, + '.bmp' : createTexture, + '.jpeg' : createTexture, + '.gif' : createTexture, + '.ico' : createTexture, + '.tiff' : createTexture, + '.webp' : createTexture, + '.image' : createTexture, + '.pvr': createTexture, + '.pkm': createTexture, + + // Audio + '.mp3' : createAudioClip, + '.ogg' : createAudioClip, + '.wav' : createAudioClip, + '.m4a' : createAudioClip, + + // Txt + '.txt' : createTextAsset, + '.xml' : createTextAsset, + '.vsh' : createTextAsset, + '.fsh' : createTextAsset, + '.atlas' : createTextAsset, + + '.tmx' : createTextAsset, + '.tsx' : createTextAsset, + '.fnt' : createTextAsset, + + '.json' : createJsonAsset, + '.ExportJson' : createJsonAsset, + + // font + '.font' : createFont, + '.eot' : createFont, + '.ttf' : createFont, + '.woff' : createFont, + '.svg' : createFont, + '.ttc' : createFont, + + // Binary + '.binary': createBufferAsset, + '.bin': createBufferAsset, + '.dbbin': createBufferAsset, + '.skel': createBufferAsset, + + 'bundle': createBundle, + + 'default': createAsset + +}; + +module.exports = factory; diff --git a/cocos2d/core/asset-manager/fetch.js b/cocos2d/core/asset-manager/fetch.js new file mode 100644 index 00000000000..ed08ebdaf93 --- /dev/null +++ b/cocos2d/core/asset-manager/fetch.js @@ -0,0 +1,129 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const packManager = require('./pack-manager'); +const Task = require('./task'); +const { getDepends, clear, forEach } = require('./utilities'); +const { assets, fetchPipeline } = require('./shared'); + +function fetch (task, done) { + + let firstTask = false; + if (!task.progress) { + task.progress = { finish: 0, total: task.input.length, canInvoke: true }; + firstTask = true; + } + + let options = task.options, depends = [], progress = task.progress, total = progress.total; + options.__exclude__ = options.__exclude__ || Object.create(null); + + task.output = []; + + forEach(task.input, function (item, cb) { + + if (!item.isNative && assets.has(item.uuid)) { + var asset = assets.get(item.uuid); + asset.addRef(); + handle(item, task, asset, null, asset.__asyncLoadAssets__, depends, total, done); + return cb(); + } + + packManager.load(item, task.options, function (err, data) { + if (err) { + if (!task.isFinish) { + if (!cc.assetManager.force || firstTask) { + cc.error(err.message, err.stack); + progress.canInvoke = false; + done(err); + } + else { + handle(item, task, null, null, false, depends, total, done); + } + } + } + else { + if (!task.isFinish) handle(item, task, null, data, !item.isNative, depends, total, done); + } + cb(); + }); + + }, function () { + + if (task.isFinish) { + clear(task, true); + return task.dispatch('error'); + } + if (depends.length > 0) { + + // stage 2 , download depend asset + let subTask = Task.create({ + name: task.name + ' dependencies', + input: depends, + progress, + options, + onProgress: task.onProgress, + onError: Task.prototype.recycle, + onComplete: function (err) { + if (!err) { + task.output.push.apply(task.output, this.output); + subTask.recycle(); + } + if (firstTask) decreaseRef(task); + done(err); + }, + }); + fetchPipeline.async(subTask); + return; + } + if (firstTask) decreaseRef(task); + done(); + }); +} + +function decreaseRef (task) { + let output = task.output; + for (let i = 0, l = output.length; i < l; i++) { + output[i].content && output[i].content.decRef(false); + } +} + +function handle (item, task, content, file, loadDepends, depends, last, done) { + + var exclude = task.options.__exclude__; + var progress = task.progress; + + item.content = content; + item.file = file; + task.output.push(item); + + if (loadDepends) { + exclude[item.uuid] = true; + getDepends(item.uuid, file || content, exclude, depends, true, false, item.config); + progress.total = last + depends.length; + } + + progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item); +} + +module.exports = fetch; \ No newline at end of file diff --git a/cocos2d/core/load-pipeline/font-loader.js b/cocos2d/core/asset-manager/font-loader.js similarity index 58% rename from cocos2d/core/load-pipeline/font-loader.js rename to cocos2d/core/asset-manager/font-loader.js index ffe56543082..9ae4442ff04 100644 --- a/cocos2d/core/load-pipeline/font-loader.js +++ b/cocos2d/core/asset-manager/font-loader.js @@ -1,7 +1,7 @@ /**************************************************************************** - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -26,13 +26,43 @@ const textUtils = require('../utils/text-utils'); let _canvasContext = null; -let _testString = "BES bswy:->@"; +// letter symbol number CJK +let _testString = "BES bswy:->@123\u4E01\u3041\u1101"; -let _fontFaces = {}; +let _fontFaces = Object.create(null); let _intervalId = -1; let _loadingFonts = []; -// 60 seconds timeout -let _timeout = 60000; +// 3 seconds timeout +let _timeout = 3000; + +// Refer to https://github.com/typekit/webfontloader/blob/master/src/core/fontwatcher.js +let useNativeCheck = (function () { + var nativeCheck = undefined; + return function () { + if (nativeCheck === undefined) { + if (!!window.FontFace) { + var match = /Gecko.*Firefox\/(\d+)/.exec(window.navigator.userAgent); + var safari10Match = /OS X.*Version\/10\..*Safari/.exec(window.navigator.userAgent) && /Apple/.exec(window.navigator.vendor); + + if (match) { + nativeCheck = parseInt(match[1], 10) > 42; + } + else if (safari10Match) { + nativeCheck = false; + } + else { + nativeCheck = true; + } + + } else { + nativeCheck = false; + } + } + + return nativeCheck; + + } +})(); function _checkFontLoaded () { let allFontsLoaded = true; @@ -44,18 +74,19 @@ function _checkFontLoaded () { // load timeout if (now - fontLoadHandle.startTime > _timeout) { cc.warnID(4933, fontFamily); - fontLoadHandle.callback(null, fontFamily); + fontLoadHandle.onComplete(null, fontFamily); _loadingFonts.splice(i, 1); continue; } let oldWidth = fontLoadHandle.refWidth; - _canvasContext.font = '40px ' + fontFamily; - let newWidth = textUtils.safeMeasureText(_canvasContext, _testString); + let fontDesc = '40px ' + fontFamily; + _canvasContext.font = fontDesc; + let newWidth = textUtils.safeMeasureText(_canvasContext, _testString, fontDesc); // loaded successfully if (oldWidth !== newWidth) { _loadingFonts.splice(i, 1); - fontLoadHandle.callback(null, fontFamily); + fontLoadHandle.onComplete(null, fontFamily); } else { allFontsLoaded = false; @@ -68,14 +99,57 @@ function _checkFontLoaded () { } } +// refer to https://github.com/typekit/webfontloader/blob/master/src/core/nativefontwatchrunner.js +function nativeCheckFontLoaded (start, font, callback) { + var loader = new Promise(function (resolve, reject) { + var check = function () { + var now = Date.now(); + + if (now - start >= _timeout) { + reject(); + } + else { + document.fonts.load('40px ' + font).then(function (fonts) { + if (fonts.length >= 1) { + resolve(); + } + else { + setTimeout(check, 100); + } + }, function () { + reject(); + }); + } + }; + + check(); + }); + + var timeoutId = null, + timer = new Promise(function (resolve, reject) { + timeoutId = setTimeout(reject, _timeout); + }); + + Promise.race([timer, loader]).then(function () { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + + callback(null, font); + }, function () { + cc.warnID(4933, font); + callback(null, font); + }); +} + var fontLoader = { - loadFont: function (item, callback) { - let url = item.url; + loadFont: function (url, options, onComplete) { let fontFamilyName = fontLoader._getFontFamily(url); // Already loaded fonts if (_fontFaces[fontFamilyName]) { - return fontFamilyName; + return onComplete(null, fontFamilyName); } if (!_canvasContext) { @@ -88,7 +162,7 @@ var fontLoader = { // Default width reference to test whether new font is loaded correctly let fontDesc = '40px ' + fontFamilyName; _canvasContext.font = fontDesc; - let refWidth = textUtils.safeMeasureText(_canvasContext, _testString); + let refWidth = textUtils.safeMeasureText(_canvasContext, _testString, fontDesc); // Setup font face style let fontStyle = document.createElement("style"); @@ -112,19 +186,23 @@ var fontLoader = { divStyle.top = "-100px"; document.body.appendChild(preloadDiv); - // Save loading font - let fontLoadHandle = { - fontFamilyName, - refWidth, - callback, - startTime: Date.now() + if (useNativeCheck()) { + nativeCheckFontLoaded(Date.now(), fontFamilyName, onComplete); } - _loadingFonts.push(fontLoadHandle); - _fontFaces[fontFamilyName] = fontStyle; - - if (_intervalId === -1) { - _intervalId = setInterval(_checkFontLoaded, 100); + else { + // Save loading font + let fontLoadHandle = { + fontFamilyName, + refWidth, + onComplete, + startTime: Date.now() + } + _loadingFonts.push(fontLoadHandle); + if (_intervalId === -1) { + _intervalId = setInterval(_checkFontLoaded, 100); + } } + _fontFaces[fontFamilyName] = fontStyle; }, _getFontFamily: function (fontHandle) { diff --git a/cocos2d/core/asset-manager/helper.js b/cocos2d/core/asset-manager/helper.js new file mode 100644 index 00000000000..57a47ab81eb --- /dev/null +++ b/cocos2d/core/asset-manager/helper.js @@ -0,0 +1,174 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const { bundles } = require('./shared'); +/** + * @module cc.AssetManager + */ +/** + * !#en + * Provide some helpful function, it is a singleton. All member can be accessed with `cc.assetManager.utils` + * + * !#zh + * 提供一些辅助方法,helper 是一个单例, 所有成员能通过 `cc.assetManager.utils` 访问 + * + * @class Helper + */ +var helper = { + /** + * !#en + * Decode uuid, returns the original uuid + * + * !#zh + * 解码 uuid,返回原始 uuid + * + * @method decodeUuid + * @param {String} base64 - the encoded uuid + * @returns {String} the original uuid + * + * @example + * var uuid = 'fcmR3XADNLgJ1ByKhqcC5Z'; + * var originalUuid = decodeUuid(uuid); // fc991dd7-0033-4b80-9d41-c8a86a702e59 + * + * @typescript + * decodeUuid(base64: string): string + */ + decodeUuid: require('../utils/decode-uuid'), + + /** + * !#en + * Extract uuid from url + * + * !#zh + * 从 url 中提取 uuid + * + * @method getUuidFromURL + * @param {String} url - url + * @returns {String} the uuid parsed from url + * + * @example + * var url = 'assets/main/import/fc/fc991dd7-0033-4b80-9d41-c8a86a702e59.json'; + * var uuid = getUuidFromURL(url); // fc991dd7-0033-4b80-9d41-c8a86a702e59 + * + * @typescript + * getUuidFromURL(url: string): string + */ + getUuidFromURL: (function () { + var _uuidRegex = /.*[/\\][0-9a-fA-F]{2}[/\\]([0-9a-fA-F-]{8,})/; + return function (url) { + var matches = url.match(_uuidRegex); + if (matches) { + return matches[1]; + } + return ''; + } + })(), + + /** + * !#en + * Transform uuid to url + * + * !#zh + * 转换 uuid 为 url + * + * @method getUrlWithUuid + * @param {string} uuid - The uuid of asset + * @param {Object} [options] - Some optional parameters + * @param {Boolean} [options.isNative] - Indicates whether the path you want is a native resource path + * @param {string} [options.nativeExt] - Extension of the native resource path, it is required when isNative is true + * @returns {string} url + * + * @example + * // json path, 'assets/main/import/fc/fc991dd7-0033-4b80-9d41-c8a86a702e59.json'; + * var url = getUrlWithUuid('fcmR3XADNLgJ1ByKhqcC5Z', {isNative: false}); + * + * // png path, 'assets/main/native/fc/fc991dd7-0033-4b80-9d41-c8a86a702e59.png'; + * var url = getUrlWithUuid('fcmR3XADNLgJ1ByKhqcC5Z', {isNative: true, nativeExt: '.png'}); + * + * @typescript + * getUrlWithUuid(uuid: string, options?: Record): string + */ + getUrlWithUuid: function (uuid, options) { + options = options || Object.create(null); + options.__isNative__ = options.isNative; + options.ext = options.nativeExt; + var bundle = bundles.find(function (bundle) { + return bundle.getAssetInfo(uuid); + }); + + if (bundle) { + options.bundle = bundle.name; + } + + return cc.assetManager._transform(uuid, options); + }, + + /** + * !#en + * Check if the type of asset is scene + * + * !#zh + * 检查资源类型是否是场景 + * + * @method isScene + * @param {*} asset - asset + * @returns {boolean} - whether or not type is cc.SceneAsset + * + * @typescript + * isScene(asset: any): boolean + */ + isScene: function (asset) { + return asset && (asset.constructor === cc.SceneAsset || asset instanceof cc.Scene); + }, + + /** + * !#en + * Normalize url, strip './' and '/' + * + * !#zh + * 标准化 url ,去除 './' 和 '/' + * + * @method normalize + * @param {string} url - url + * @returns {string} - The normalized url + * + * @typescript + * normalize(url: string): string + */ + normalize: function (url) { + if (url) { + if (url.charCodeAt(0) === 46 && url.charCodeAt(1) === 47) { + // strip './' + url = url.slice(2); + } + else if (url.charCodeAt(0) === 47) { + // strip '/' + url = url.slice(1); + } + } + return url; + } +}; + +module.exports = helper; \ No newline at end of file diff --git a/cocos2d/core/load-pipeline/index.js b/cocos2d/core/asset-manager/index.js similarity index 83% rename from cocos2d/core/load-pipeline/index.js rename to cocos2d/core/asset-manager/index.js index b93a25525db..414d4c87c82 100644 --- a/cocos2d/core/load-pipeline/index.js +++ b/cocos2d/core/asset-manager/index.js @@ -1,8 +1,7 @@ /**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -23,11 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ +require('./deprecated'); +require('./CCAssetManager'); -require('./downloader'); -require('./loader'); - -require('./loading-items'); -require('./pipeline'); - -require('./CCLoader'); diff --git a/cocos2d/core/asset-manager/load.js b/cocos2d/core/asset-manager/load.js new file mode 100644 index 00000000000..04cab5085ad --- /dev/null +++ b/cocos2d/core/asset-manager/load.js @@ -0,0 +1,240 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const packManager = require('./pack-manager'); +const Pipeline = require('./pipeline'); +const parser = require('./parser'); +const { getDepends, cache, gatherAsset, setProperties, forEach, clear, checkCircleReference } = require('./utilities'); +const { assets, files, parsed, pipeline } = require('./shared'); +const Task = require('./task'); + +function load (task, done) { + + let firstTask = false; + if (!task.progress) { + task.progress = { finish: 0, total: task.input.length, canInvoke: true }; + firstTask = true; + } + + var options = task.options, progress = task.progress; + + options.__exclude__ = options.__exclude__ || Object.create(null); + + task.output = []; + + forEach(task.input, function (item, cb) { + + let subTask = Task.create({ + input: item, + onProgress: task.onProgress, + options, + progress, + onComplete: function (err, item) { + if (err && !task.isFinish) { + if (!cc.assetManager.force || firstTask) { + cc.error(err.message, err.stack); + progress.canInvoke = false; + done(err); + } + else { + progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item); + } + } + task.output.push(item); + subTask.recycle(); + cb(); + } + }); + + loadOneAssetPipeline.async(subTask); + + }, function () { + + options.__exclude__ = null; + + if (task.isFinish) { + clear(task, true); + return task.dispatch('error'); + } + + gatherAsset(task); + clear(task, true); + done(); + }); +} + +var loadOneAssetPipeline = new Pipeline('loadOneAsset', [ + + function fetch (task, done) { + var item = task.output = task.input; + var { options, isNative, uuid, file } = item; + var { reload } = options; + + if (file || (!reload && !isNative && assets.has(uuid))) return done(); + + packManager.load(item, task.options, function (err, data) { + item.file = data; + done(err); + }); + }, + + function parse (task, done) { + + var item = task.output = task.input, progress = task.progress, exclude = task.options.__exclude__; + var { id, file, options } = item; + + if (item.isNative) { + parser.parse(id, file, item.ext, options, function (err, asset) { + if (err) return done(err); + item.content = asset; + progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item); + files.remove(id); + parsed.remove(id); + done(); + }); + } + else { + var { uuid } = item; + if (uuid in exclude) { + + var { finish, content, err, callbacks } = exclude[uuid]; + progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item); + + if (finish || checkCircleReference(uuid, uuid, exclude) ) { + content && content.addRef && content.addRef(); + item.content = content; + done(err); + } + else { + callbacks.push({ done, item }); + } + } + else { + if (!options.reload && assets.has(uuid)) { + var asset = assets.get(uuid); + if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) { + item.content = asset.addRef(); + progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item); + done(); + } + else { + loadDepends(task, asset, done, false); + } + } + else { + parser.parse(id, file, 'import', options, function (err, asset) { + if (err) return done(err); + asset._uuid = uuid; + loadDepends(task, asset, done, true); + }); + } + } + } + } +]); + +function loadDepends (task, asset, done, init) { + + var item = task.input, progress = task.progress; + var { uuid, id, options, config } = item; + var { __asyncLoadAssets__, cacheAsset } = options; + + var depends = []; + // add reference avoid being released during loading dependencies + asset.addRef && asset.addRef(); + getDepends(uuid, asset, Object.create(null), depends, false, __asyncLoadAssets__, config); + progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total += depends.length, item); + + var repeatItem = task.options.__exclude__[uuid] = { content: asset, finish: false, callbacks: [{ done, item }] }; + + let subTask = Task.create({ + input: depends, + options: task.options, + onProgress: task.onProgress, + onError: Task.prototype.recycle, + progress, + onComplete: function (err) { + asset.decRef && asset.decRef(false); + asset.__asyncLoadAssets__ = __asyncLoadAssets__; + repeatItem.finish = true; + repeatItem.err = err; + + if (!err) { + + var assets = Array.isArray(subTask.output) ? subTask.output : [subTask.output]; + var map = Object.create(null); + for (let i = 0, l = assets.length; i < l; i++) { + var dependAsset = assets[i]; + dependAsset && (map[dependAsset instanceof cc.Asset ? dependAsset._uuid + '@import' : uuid + '@native'] = dependAsset); + } + + if (!init) { + if (asset.__nativeDepend__ && !asset._nativeAsset) { + var missingAsset = setProperties(uuid, asset, map); + if (!missingAsset) { + try { + asset.onLoad && asset.onLoad(); + } + catch (e) { + cc.error(e.message, e.stack); + } + } + } + } + else { + var missingAsset = setProperties(uuid, asset, map); + if (!missingAsset) { + try { + asset.onLoad && asset.onLoad(); + } + catch (e) { + cc.error(e.message, e.stack); + } + } + files.remove(id); + parsed.remove(id); + cache(uuid, asset, cacheAsset !== undefined ? cacheAsset : cc.assetManager.cacheAsset); + } + subTask.recycle(); + } + + var callbacks = repeatItem.callbacks; + + for (var i = 0, l = callbacks.length; i < l; i++) { + + var cb = callbacks[i]; + asset.addRef && asset.addRef(); + cb.item.content = asset; + cb.done(err); + + } + + callbacks.length = 0; + } + }); + + pipeline.async(subTask); +} + +module.exports = load; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/pack-manager.js b/cocos2d/core/asset-manager/pack-manager.js new file mode 100644 index 00000000000..8d1fc27057f --- /dev/null +++ b/cocos2d/core/asset-manager/pack-manager.js @@ -0,0 +1,264 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +import { unpackJSONs, packCustomObjData } from '../platform/deserialize-compiled'; + +const downloader = require('./downloader'); +const Cache = require('./cache'); +const js = require('../platform/js'); +const { files } = require('./shared'); + +var _loading = new Cache(); + +function isLoading (val) { + return _loading.has(val.uuid); +} + + +/** + * @module cc.AssetManager + */ +/** + * !#en + * Handle the packed asset, include unpacking, loading, cache and so on. It is a singleton. All member can be accessed with `cc.assetManager.packManager` + * + * !#zh + * 处理打包资源,包括拆包,加载,缓存等等,这是一个单例, 所有成员能通过 `cc.assetManager.packManager` 访问 + * + * @class PackManager + */ +var packManager = { + + /** + * !#en + * Unpack the json, revert to what it was before packing + * + * !#zh + * 拆解 json 包,恢复为打包之前的内容 + * + * @method unpackJson + * @param {String[]} pack - The pack + * @param {Object} json - The content of pack + * @param {Object} options - Some optional parameters + * @param {Function} onComplete - Callback when finish unpacking + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {Object} onComplete.content - The unpacked assets + * + * @example + * downloader.downloadFile('pack.json', {responseType: 'json'}, null, (err, file) => { + * packManager.unpackJson(['a', 'b'], file, null, (err, data) => console.log(err)); + * }); + * + * @typescript + * unpackJson(pack: string[], json: any, options: Record, onComplete?: (err: Error, content: any) => void): void + */ + unpackJson (pack, json, options, onComplete) { + + var out = js.createMap(true), err = null; + + if (Array.isArray(json)) { + + json = unpackJSONs(json, cc._MissingScript.safeFindClass); + + if (json.length !== pack.length) { + cc.errorID(4915); + } + for (let i = 0; i < pack.length; i++) { + var key = pack[i] + '@import'; + out[key] = json[i]; + } + } + else { + const textureType = js._getClassId(cc.Texture2D); + if (json.type === textureType) { + if (json.data) { + var datas = json.data.split('|'); + if (datas.length !== pack.length) { + cc.errorID(4915); + } + for (let i = 0; i < pack.length; i++) { + out[pack[i] + '@import'] = packCustomObjData(textureType, datas[i]); + } + } + } + else { + err = new Error('unmatched type pack!'); + out = null; + } + } + onComplete && onComplete(err, out); + }, + + init () { + _loading.clear(); + }, + + /** + * !#en + * Register custom handler if you want to change default behavior or extend packManager to unpack other format pack + * + * !#zh + * 当你想修改默认行为或者拓展 packManager 来拆分其他格式的包时可以注册自定义的 handler + * + * @method register + * @param {string|Object} type - Extension likes '.bin' or map likes {'.bin': binHandler, '.ab': abHandler} + * @param {Function} [handler] - handler + * @param {string} handler.packUuid - The uuid of pack + * @param {*} handler.data - The content of pack + * @param {Object} handler.options - Some optional parameters + * @param {Function} handler.onComplete - Callback when finishing unpacking + * + * @example + * packManager.register('.bin', (packUuid, file, options, onComplete) => onComplete(null, null)); + * packManager.register({'.bin': (packUuid, file, options, onComplete) => onComplete(null, null), '.ab': (packUuid, file, options, onComplete) => onComplete(null, null)}); + * + * @typescript + * register(type: string, handler: (packUuid: string, data: any, options: Record, onComplete: (err: Error, content: any) => void) => void): void + * register(map: Record, onComplete: (err: Error, content: any) => void) => void>): void + */ + register (type, handler) { + if (typeof type === 'object') { + js.mixin(unpackers, type); + } + else { + unpackers[type] = handler; + } + }, + + /** + * !#en + * Use corresponding handler to unpack package + * + * !#zh + * 用对应的 handler 来进行解包 + * + * @method unpack + * @param {String[]} pack - The uuid of packed assets + * @param {*} data - The packed data + * @param {string} type - The type indicates that which handler should be used to download, such as '.jpg' + * @param {Object} options - Some optional parameter + * @param {Function} onComplete - callback when finishing unpacking + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {*} onComplete.data - Original assets + * + * @example + * downloader.downloadFile('pack.json', {responseType: 'json'}, null, (err, file) => { + * packManager.unpack(['2fawq123d', '1zsweq23f'], file, '.json', null, (err, data) => console.log(err)); + * }); + * + * @typescript + * unpack(pack: string[], data: any, type: string, options: Record, onComplete?: (err: Error, data: any) => void): void + */ + unpack (pack, data, type, options, onComplete) { + if (!data) { + onComplete && onComplete(new Error('package data is wrong!')); + return; + } + var unpacker = unpackers[type]; + unpacker(pack, data, options, onComplete); + }, + + /** + * !#en + * Download request item, If item is not in any package, download as usual. Otherwise, download the corresponding package and unpack it. + * And then retrieve the corresponding content form it. + * + * !#zh + * 下载请求对象,如果请求对象不在任何包内,则正常下载,否则下载对应的 package 并进行拆解,并取回包内对应的内容 + * + * @method load + * @param {RequestItem} item - Some item you want to download + * @param {Object} options - Some optional parameters + * @param {Function} onComplete - Callback when finished + * @param {Err} onComplete.err - The occurred error, null indicetes success + * @param {*} onComplete.data - The unpacked data retrieved from package + * + * @example + * var requestItem = cc.AssetManager.RequestItem.create(); + * requestItem.uuid = 'fcmR3XADNLgJ1ByKhqcC5Z'; + * requestItem.info = config.getAssetInfo('fcmR3XADNLgJ1ByKhqcC5Z'); + * packManager.load(requestItem, null, (err, data) => console.log(err)); + * + * @typescript + * load(item: RequestItem, options: Record, onComplete: (err: Error, data: any) => void): void + * + */ + load (item, options, onComplete) { + // if not in any package, download as uausl + if (item.isNative || !item.info || !item.info.packs) return downloader.download(item.id, item.url, item.ext, item.options, onComplete); + + if (files.has(item.id)) return onComplete(null, files.get(item.id)); + + var packs = item.info.packs; + + // find a loading package + var pack = packs.find(isLoading); + + if (pack) return _loading.get(pack.uuid).push({ onComplete, id: item.id }); + + // download a new package + pack = packs[0]; + _loading.add(pack.uuid, [{ onComplete, id: item.id }]); + + let url = cc.assetManager._transform(pack.uuid, {ext: pack.ext, bundle: item.config.name}); + + downloader.download(pack.uuid, url, pack.ext, item.options, function (err, data) { + files.remove(pack.uuid); + if (err) { + cc.error(err.message, err.stack); + } + // unpack package + packManager.unpack(pack.packs, data, pack.ext, item.options, function (err, result) { + if (!err) { + for (var id in result) { + files.add(id, result[id]); + } + } + var callbacks = _loading.remove(pack.uuid); + for (var i = 0, l = callbacks.length; i < l; i++) { + var cb = callbacks[i]; + if (err) { + cb.onComplete(err); + continue; + } + + var data = result[cb.id]; + if (!data) { + cb.onComplete(new Error('can not retrieve data from package')); + } + else { + cb.onComplete(null, data); + } + } + }); + }); + } +}; + +var unpackers = { + '.json': packManager.unpackJson +}; + +module.exports = packManager; diff --git a/cocos2d/core/asset-manager/parser.js b/cocos2d/core/asset-manager/parser.js new file mode 100644 index 00000000000..03e803c4077 --- /dev/null +++ b/cocos2d/core/asset-manager/parser.js @@ -0,0 +1,440 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +/** + * @module cc.AssetManager + */ + +const plistParser = require('../platform/CCSAXParser').plistParser; +const js = require('../platform/js'); +const deserialize = require('./deserialize'); +const Cache = require('./cache'); +const { isScene } = require('./helper'); +const { parsed, files } = require('./shared'); +const { __audioSupport, capabilities } = require('../platform/CCSys'); + +var _parsing = new Cache(); + +/** + * !#en + * Parse the downloaded file, it's a singleton, all member can be accessed with `cc.assetManager.parser` + * + * !#zh + * 解析已下载的文件,parser 是一个单例, 所有成员能通过 `cc.assetManaager.parser` 访问 + * + * @class Parser + */ +var parser = { + /* + * !#en + * Parse image file + * + * !#zh + * 解析图片文件 + * + * @method parseImage + * @param {Blob} file - The downloaded file + * @param {Object} options - Some optional paramters + * @param {Function} [onComplete] - callback when finish parsing. + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {ImageBitmap|HTMLImageElement} onComplete.img - The parsed content + * + * @example + * downloader.downloadFile('test.jpg', {responseType: 'blob'}, null, (err, file) => { + * parser.parseImage(file, null, (err, img) => console.log(err)); + * }); + * + * @typescript + * parseImage(file: Blob, options: Record, onComplete?: (err: Error, img: ImageBitmap|HTMLImageElement) => void): void + */ + parseImage (file, options, onComplete) { + if (capabilities.imageBitmap && file instanceof Blob) { + let imageOptions = {}; + imageOptions.imageOrientation = options.__flipY__ ? 'flipY' : 'none'; + imageOptions.premultiplyAlpha = options.__premultiplyAlpha__ ? 'premultiply' : 'none'; + createImageBitmap(file, imageOptions).then(function (result) { + result.flipY = !!options.__flipY__; + result.premultiplyAlpha = !!options.__premultiplyAlpha__; + onComplete && onComplete(null, result); + }, function (err) { + onComplete && onComplete(err, null); + }); + } + else { + onComplete && onComplete(null, file); + } + }, + + /* + * !#en + * Parse audio file + * + * !#zh + * 解析音频文件 + * + * @method parseAudio + * @param {ArrayBuffer|HTMLAudioElement} file - The downloaded file + * @param {Object} options - Some optional paramters + * @param {Function} onComplete - Callback when finish parsing. + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {AudioBuffer|HTMLAudioElement} onComplete.audio - The parsed content + * + * @example + * downloader.downloadFile('test.mp3', {responseType: 'arraybuffer'}, null, (err, file) => { + * parser.parseAudio(file, null, (err, audio) => console.log(err)); + * }); + * + * @typescript + * parseAudio(file: ArrayBuffer|HTMLAudioElement, options: Record, onComplete?: (err: Error, audio: AudioBuffer|HTMLAudioElement) => void): void + */ + parseAudio (file, options, onComplete) { + if (file instanceof ArrayBuffer) { + __audioSupport.context.decodeAudioData(file, function (buffer) { + onComplete && onComplete(null, buffer); + }, function(e){ + onComplete && onComplete(e, null); + }); + } + else { + onComplete && onComplete(null, file); + } + }, + + /* + * !#en + * Parse pvr file + * + * !#zh + * 解析压缩纹理格式 pvr 文件 + * + * @method parsePVRTex + * @param {ArrayBuffer|ArrayBufferView} file - The downloaded file + * @param {Object} options - Some optional paramters + * @param {Function} onComplete - Callback when finish parsing. + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {Object} onComplete.pvrAsset - The parsed content + * + * @example + * downloader.downloadFile('test.pvr', {responseType: 'arraybuffer'}, null, (err, file) => { + * parser.parsePVRTex(file, null, (err, pvrAsset) => console.log(err)); + * }); + * + * @typescript + * parsePVRTex(file: ArrayBuffer|ArrayBufferView, options: Record, onComplete: (err: Error, pvrAsset: {_data: Uint8Array, _compressed: boolean, width: number, height: number}) => void): void + */ + parsePVRTex : (function () { + //===============// + // PVR constants // + //===============// + // https://github.com/toji/texture-tester/blob/master/js/webgl-texture-util.js#L424 + const PVR_HEADER_LENGTH = 13; // The header length in 32 bit ints. + const PVR_MAGIC = 0x03525650; //0x50565203; + + // Offsets into the header array. + const PVR_HEADER_MAGIC = 0; + const PVR_HEADER_FORMAT = 2; + const PVR_HEADER_HEIGHT = 6; + const PVR_HEADER_WIDTH = 7; + const PVR_HEADER_MIPMAPCOUNT = 11; + const PVR_HEADER_METADATA = 12; + + return function (file, options, onComplete) { + let err = null, out = null; + try { + let buffer = file instanceof ArrayBuffer ? file : file.buffer; + // Get a view of the arrayBuffer that represents the DDS header. + let header = new Int32Array(buffer, 0, PVR_HEADER_LENGTH); + + // Do some sanity checks to make sure this is a valid DDS file. + if(header[PVR_HEADER_MAGIC] != PVR_MAGIC) { + throw new Error("Invalid magic number in PVR header"); + } + + // Gather other basic metrics and a view of the raw the DXT data. + let width = header[PVR_HEADER_WIDTH]; + let height = header[PVR_HEADER_HEIGHT]; + let dataOffset = header[PVR_HEADER_METADATA] + 52; + let pvrtcData = new Uint8Array(buffer, dataOffset); + + out = { + _data: pvrtcData, + _compressed: true, + width: width, + height: height, + }; + + } + catch (e) { + err = e; + } + onComplete && onComplete(err, out); + }; + })(), + + /* + * !#en + * Parse pkm file + * + * !#zh + * 解析压缩纹理格式 pkm 文件 + * + * @method parsePKMTex + * @param {ArrayBuffer|ArrayBufferView} file - The downloaded file + * @param {Object} options - Some optional paramters + * @param {Function} onComplete - Callback when finish parsing. + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {Object} onComplete.etcAsset - The parsed content + * + * @example + * downloader.downloadFile('test.pkm', {responseType: 'arraybuffer'}, null, (err, file) => { + * parser.parsePKMTex(file, null, (err, etcAsset) => console.log(err)); + * }); + * + * @typescript + * parsePKMTex(file: ArrayBuffer|ArrayBufferView, options: Record, onComplete: (err: Error, etcAsset: {_data: Uint8Array, _compressed: boolean, width: number, height: number}) => void): void + */ + parsePKMTex: (function () { + //===============// + // ETC constants // + //===============// + const ETC_PKM_HEADER_SIZE = 16; + + const ETC_PKM_FORMAT_OFFSET = 6; + const ETC_PKM_ENCODED_WIDTH_OFFSET = 8; + const ETC_PKM_ENCODED_HEIGHT_OFFSET = 10; + const ETC_PKM_WIDTH_OFFSET = 12; + const ETC_PKM_HEIGHT_OFFSET = 14; + + const ETC1_RGB_NO_MIPMAPS = 0; + const ETC2_RGB_NO_MIPMAPS = 1; + const ETC2_RGBA_NO_MIPMAPS = 3; + + function readBEUint16(header, offset) { + return (header[offset] << 8) | header[offset+1]; + } + return function (file, options, onComplete) { + let err = null, out = null; + try { + let buffer = file instanceof ArrayBuffer ? file : file.buffer; + let header = new Uint8Array(buffer); + let format = readBEUint16(header, ETC_PKM_FORMAT_OFFSET); + if (format !== ETC1_RGB_NO_MIPMAPS && format !== ETC2_RGB_NO_MIPMAPS && format !== ETC2_RGBA_NO_MIPMAPS) { + return new Error("Invalid magic number in ETC header"); + } + let width = readBEUint16(header, ETC_PKM_WIDTH_OFFSET); + let height = readBEUint16(header, ETC_PKM_HEIGHT_OFFSET); + let encodedWidth = readBEUint16(header, ETC_PKM_ENCODED_WIDTH_OFFSET); + let encodedHeight = readBEUint16(header, ETC_PKM_ENCODED_HEIGHT_OFFSET); + let etcData = new Uint8Array(buffer, ETC_PKM_HEADER_SIZE); + out = { + _data: etcData, + _compressed: true, + width: width, + height: height + }; + + } + catch (e) { + err = e; + } + onComplete && onComplete(err, out); + } + })(), + + /* + * !#en + * Parse plist file + * + * !#zh + * 解析 plist 文件 + * + * @method parsePlist + * @param {string} file - The downloaded file + * @param {Object} options - Some optional paramters + * @param {Function} onComplete - Callback when finish parsing + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {*} onComplete.data - The parsed content + * + * @example + * downloader.downloadFile('test.plist', {responseType: 'text'}, null, (err, file) => { + * parser.parsePlist(file, null, (err, data) => console.log(err)); + * }); + * + * @typescript + * parsePlist(file: string, options: Record, onComplete?: (err: Error, data: any) => void): void + */ + parsePlist (file, options, onComplete) { + var err = null; + var result = plistParser.parse(file); + if (!result) err = new Error('parse failed'); + onComplete && onComplete(err, result); + }, + + /* + * !#en + * Deserialize asset file + * + * !#zh + * 反序列化资源文件 + * + * @method parseImport + * @param {Object} file - The serialized json + * @param {Object} options - Some optional paramters + * @param {Function} onComplete - Callback when finish parsing + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {Asset} onComplete.asset - The parsed content + * + * @example + * downloader.downloadFile('test.json', {responseType: 'json'}, null, (err, file) => { + * parser.parseImport(file, null, (err, data) => console.log(err)); + * }); + * + * @typescript + * parseImport (file: any, options: Record, onComplete?: (err: Error, asset: cc.Asset) => void): void + */ + parseImport (file, options, onComplete) { + if (!file) return onComplete && onComplete(new Error('Json is empty')); + var result, err = null; + try { + result = deserialize(file, options); + } + catch (e) { + err = e; + } + onComplete && onComplete(err, result); + }, + + init () { + _parsing.clear(); + }, + + /** + * !#en + * Register custom handler if you want to change default behavior or extend parser to parse other format file + * + * !#zh + * 当你想修改默认行为或者拓展 parser 来解析其他格式文件时可以注册自定义的handler + * + * @method register + * @param {string|Object} type - Extension likes '.jpg' or map likes {'.jpg': jpgHandler, '.png': pngHandler} + * @param {Function} [handler] - The corresponding handler + * @param {*} handler.file - File + * @param {Object} handler.options - Some optional paramter + * @param {Function} handler.onComplete - callback when finishing parsing + * + * @example + * parser.register('.tga', (file, options, onComplete) => onComplete(null, null)); + * parser.register({'.tga': (file, options, onComplete) => onComplete(null, null), '.ext': (file, options, onComplete) => onComplete(null, null)}); + * + * @typescript + * register(type: string, handler: (file: any, options: Record, onComplete: (err: Error, data: any) => void) => void): void + * register(map: Record, onComplete: (err: Error, data: any) => void) => void>): void + */ + register (type, handler) { + if (typeof type === 'object') { + js.mixin(parsers, type); + } + else { + parsers[type] = handler; + } + }, + + /** + * !#en + * Use corresponding handler to parse file + * + * !#zh + * 使用对应的handler来解析文件 + * + * @method parse + * @param {string} id - The id of file + * @param {*} file - File + * @param {string} type - The corresponding type of file, likes '.jpg'. + * @param {Object} options - Some optional paramters will be transferred to the corresponding handler. + * @param {Function} onComplete - callback when finishing downloading + * @param {Error} onComplete.err - The occurred error, null indicetes success + * @param {*} onComplete.contetnt - The parsed file + * + * @example + * downloader.downloadFile('test.jpg', {responseType: 'blob'}, null, (err, file) => { + * parser.parse('test.jpg', file, '.jpg', null, (err, img) => console.log(err)); + * }); + * + * @typescript + * parse(id: string, file: any, type: string, options: Record, onComplete: (err: Error, content: any) => void): void + */ + parse (id, file, type, options, onComplete) { + let parsedAsset, parsing, parseHandler; + if (parsedAsset = parsed.get(id)) { + onComplete(null, parsedAsset); + } + else if (parsing = _parsing.get(id)){ + parsing.push(onComplete); + } + else if (parseHandler = parsers[type]){ + _parsing.add(id, [onComplete]); + parseHandler(file, options, function (err, data) { + if (err) { + files.remove(id); + } + else if (!isScene(data)){ + parsed.add(id, data); + } + let callbacks = _parsing.remove(id); + for (let i = 0, l = callbacks.length; i < l; i++) { + callbacks[i](err, data); + } + }); + } + else { + onComplete(null, file); + } + } +}; + +var parsers = { + '.png' : parser.parseImage, + '.jpg' : parser.parseImage, + '.bmp' : parser.parseImage, + '.jpeg' : parser.parseImage, + '.gif' : parser.parseImage, + '.ico' : parser.parseImage, + '.tiff' : parser.parseImage, + '.webp' : parser.parseImage, + '.image' : parser.parseImage, + '.pvr' : parser.parsePVRTex, + '.pkm' : parser.parsePKMTex, + // Audio + '.mp3' : parser.parseAudio, + '.ogg' : parser.parseAudio, + '.wav' : parser.parseAudio, + '.m4a' : parser.parseAudio, + + // plist + '.plist' : parser.parsePlist, + 'import' : parser.parseImport +}; + +module.exports = parser; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/pipeline.js b/cocos2d/core/asset-manager/pipeline.js new file mode 100644 index 00000000000..a66267a417f --- /dev/null +++ b/cocos2d/core/asset-manager/pipeline.js @@ -0,0 +1,330 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +/** + * @module cc.AssetManager + */ + +const Task = require('./task'); + +var _pipelineId = 0; +/** + * !#en + * Pipeline can execute the task for some effect. + * + * !#zh + * 管线能执行任务达到某个效果 + * + * @class Pipeline + */ +function Pipeline (name, funcs) { + if (!Array.isArray(funcs)) { + cc.warn('funcs must be an array'); + return; + } + + /** + * !#en + * The id of pipeline + * + * !#zh + * 管线的 id + * + * @property id + * @type {Number} + */ + this.id = _pipelineId++; + + /** + * !#en + * The name of pipeline + * + * !#zh + * 管线的名字 + * + * @property name + * @type {String} + */ + this.name = name; + + /** + * !#en + * All pipes of pipeline + * + * !#zh + * 所有的管道 + * + * @property pipes + * @type {Function[]} + */ + this.pipes = []; + + for (var i = 0, l = funcs.length; i < l; i++) { + if (typeof funcs[i] === 'function') { + this.pipes.push(funcs[i]); + } + } + +} + +Pipeline.prototype = { + + + /** + * !#en + * Create a new pipeline + * + * !#zh + * 创建一个管线 + * + * @method constructor + * @param {string} name - The name of pipeline + * @param {Function[]} funcs - The array of pipe, every pipe must be function which take two parameters, the first is a `Task` flowed in pipeline, the second is complete callback + * + * @example + * var pipeline = new Pipeline('download', [ + * (task, done) => { + * var url = task.input; + * cc.assetManager.downloader.downloadFile(url, null, null, (err, result) => { + * task.output = result; + * done(err); + * }); + * }, + * (task, done) => { + * var text = task.input; + * var json = JSON.stringify(text); + * task.output = json; + * done(); + * } + * ]); + * + * @typescript + * constructor(name: string, funcs: Array<(task: Task, done?: (err: Error) => void) => void>) + */ + constructor: Pipeline, + + /** + * !#en + * At specific point insert a new pipe to pipeline + * + * !#zh + * 在某个特定的点为管线插入一个新的 pipe + * + * @method insert + * @param {Function} func - The new pipe + * @param {Task} func.task - The task handled with pipeline will be transferred to this function + * @param {Function} [func.callback] - Callback you need to invoke manually when this pipe is finished. if the pipeline is synchronous, callback is unnecessary. + * @param {number} index - The specific point you want to insert at. + * @return {Pipeline} pipeline + * + * @example + * var pipeline = new Pipeline('test', []); + * pipeline.insert((task, done) => { + * // do something + * done(); + * }, 0); + * + * @typescript + * insert(func: (task: Task, callback?: (err: Error) => void) => void, index: number): Pipeline + */ + insert (func, index) { + if (typeof func !== 'function' || index > this.pipes.length) { + cc.warnID(4921); + return; + } + + this.pipes.splice(index, 0, func); + return this; + }, + + + /** + * !#en + * Append a new pipe to the pipeline + * + * !#zh + * 添加一个管道到管线中 + * + * @method append + * @param {Function} func - The new pipe + * @param {Task} func.task - The task handled with pipeline will be transferred to this function + * @param {Function} [func.callback] - Callback you need to invoke manually when this pipe is finished. if the pipeline is synchronous, callback is unnecessary. + * @return {Pipeline} pipeline + * + * @example + * var pipeline = new Pipeline('test', []); + * pipeline.append((task, done) => { + * // do something + * done(); + * }); + * + * @typescript + * append(func: (task: Task, callback?: (err: Error) => void) => void): Pipeline + */ + append (func) { + if (typeof func !== 'function') { + return; + } + + this.pipes.push(func); + return this; + }, + + /** + * !#en + * Remove pipe which at specific point + * + * !#zh + * 移除特定位置的管道 + * + * @method remove + * @param {number} index - The specific point + * @return {Pipeline} pipeline + * + * @example + * var pipeline = new Pipeline('test', (task, done) => { + * // do something + * done(); + * }); + * pipeline.remove(0); + * + * @typescript + * remove(index: number): Pipeline + */ + remove (index) { + if (typeof index !== 'number') { + return; + } + + this.pipes.splice(index, 1); + return this; + }, + + /** + * !#en + * Execute task synchronously + * + * !#zh + * 同步执行任务 + * + * @method sync + * @param {Task} task - The task will be executed + * @returns {*} result + * + * @example + * var pipeline = new Pipeline('sync', [(task) => { + * let input = task.input; + * task.output = doSomething(task.input); + * }]); + * + * var task = new Task({input: 'test'}); + * console.log(pipeline.sync(task)); + * + * @typescript + * sync(task: Task): any + */ + sync (task) { + var pipes = this.pipes; + if (!(task instanceof Task) || pipes.length === 0) return; + if (task.output != null) { + task.input = task.output; + task.output = null; + } + task._isFinish = false; + for (var i = 0, l = pipes.length; i < l;) { + var pipe = pipes[i]; + var result = pipe(task); + if (result) { + task._isFinish = true; + return result; + } + i++; + if (i !== l) { + task.input = task.output; + task.output = null; + } + } + task._isFinish = true; + return task.output; + }, + + /** + * !#en + * Execute task asynchronously + * + * !#zh + * 异步执行任务 + * + * @method async + * @param {Task} task - The task will be executed + * + * @example + * var pipeline = new Pipeline('sync', [(task, done) => { + * let input = task.input; + * task.output = doSomething(task.input); + * done(); + * }]); + * var task = new Task({input: 'test', onComplete: (err, result) => console.log(result)}); + * pipeline.async(task); + * + * @typescript + * async(task: Task): void + */ + async (task) { + var pipes = this.pipes; + if (!(task instanceof Task) || pipes.length === 0) return; + if (task.output != null) { + task.input = task.output; + task.output = null; + } + task._isFinish = false; + this._flow(0, task); + }, + + _flow (index, task) { + var self = this; + var pipe = this.pipes[index]; + pipe(task, function (result) { + if (result) { + task._isFinish = true; + task.onComplete && task.onComplete(result); + } + else { + index++; + if (index < self.pipes.length) { + // move output to input + task.input = task.output; + task.output = null; + self._flow(index, task); + } + else { + task._isFinish = true; + task.onComplete && task.onComplete(result, task.output); + } + } + }); + } +}; + +module.exports = Pipeline; diff --git a/cocos2d/core/asset-manager/preprocess.js b/cocos2d/core/asset-manager/preprocess.js new file mode 100644 index 00000000000..accfe223fdc --- /dev/null +++ b/cocos2d/core/asset-manager/preprocess.js @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const Task = require('./task'); +const { transformPipeline, RequestType } = require('./shared'); + +function preprocess (task, done) { + var options = task.options, subOptions = Object.create(null), leftOptions = Object.create(null); + + for (var op in options) { + switch (op) { + // can't set these attributes in options + case RequestType.PATH: + case RequestType.UUID: + case RequestType.DIR: + case RequestType.SCENE: + case RequestType.URL : break; + // only need these attributes to transform url + case '__requestType__': + case '__isNative__': + case 'ext' : + case 'type': + case '__nativeName__': + case 'audioLoadMode': + case 'bundle': + subOptions[op] = options[op]; + break; + // other settings, left to next pipe + case '__exclude__': + case '__outputAsArray__': + leftOptions[op] = options[op]; + break; + default: + subOptions[op] = options[op]; + leftOptions[op] = options[op]; + break; + } + } + task.options = leftOptions; + + // transform url + let subTask = Task.create({input: task.input, options: subOptions}); + var err = null; + try { + task.output = task.source = transformPipeline.sync(subTask); + } + catch (e) { + err = e; + for (var i = 0, l = subTask.output.length; i < l; i++) { + subTask.output[i].recycle(); + } + } + subTask.recycle(); + done(err); +} + +module.exports = preprocess; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/releaseManager.js b/cocos2d/core/asset-manager/releaseManager.js new file mode 100644 index 00000000000..cb35ce147ef --- /dev/null +++ b/cocos2d/core/asset-manager/releaseManager.js @@ -0,0 +1,238 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const dependUtil = require('./depend-util'); +const Cache = require('./cache'); +require('../assets/CCAsset'); +const { assets } = require('./shared'); + +function visitAsset (asset, deps) { + // Skip assets generated programmatically or by user (e.g. label texture) + if (!asset._uuid) { + return; + } + deps.push(asset._uuid); +} + +function visitComponent (comp, deps) { + var props = Object.getOwnPropertyNames(comp); + for (let i = 0; i < props.length; i++) { + var propName = props[i]; + if (propName === 'node' || propName === '__eventTargets') continue; + var value = comp[propName]; + if (typeof value === 'object' && value) { + if (Array.isArray(value)) { + for (let j = 0; j < value.length; j++) { + let val = value[j]; + if (val instanceof cc.Asset) { + visitAsset(val, deps); + } + } + } + else if (!value.constructor || value.constructor === Object) { + let keys = Object.getOwnPropertyNames(value); + for (let j = 0; j < keys.length; j++) { + let val = value[keys[j]]; + if (val instanceof cc.Asset) { + visitAsset(val, deps); + } + } + } + else if (value instanceof cc.Asset) { + visitAsset(value, deps); + } + } + } +} + +let _temp = []; + +function visitNode (node, deps) { + for (let i = 0; i < node._components.length; i++) { + visitComponent(node._components[i], deps); + } + for (let i = 0; i < node._children.length; i++) { + visitNode(node._children[i], deps); + } +} + +function descendOpRef (asset, refs, exclude, op) { + exclude.push(asset._uuid); + var depends = dependUtil.getDeps(asset._uuid); + for (let i = 0, l = depends.length; i < l; i++) { + var dependAsset = assets.get(depends[i]); + if (dependAsset) { + let uuid = dependAsset._uuid; + if (!(uuid in refs)) { + refs[uuid] = dependAsset.refCount + op; + } + else { + refs[uuid] += op; + } + if (exclude.includes(uuid)) continue; + descendOpRef(dependAsset, refs, exclude, op); + } + } +} + +function checkCircularReference (asset) { + // check circular reference + var refs = Object.create(null); + refs[asset._uuid] = asset.refCount; + descendOpRef(asset, refs, _temp, -1); + _temp.length = 0; + if (refs[asset._uuid] !== 0) return refs[asset._uuid]; + + for (let uuid in refs) { + if (refs[uuid] !== 0) { + descendOpRef(assets.get(uuid), refs, _temp, 1); + } + } + _temp.length = 0; + + return refs[asset._uuid]; +} + +var _persistNodeDeps = new Cache(); +var _toDelete = new Cache(); +var eventListener = false; + +function freeAssets () { + eventListener = false; + _toDelete.forEach(function (asset) { + releaseManager._free(asset); + }); + _toDelete.clear(); +} + +var releaseManager = { + init () { + _persistNodeDeps.clear(); + _toDelete.clear(); + }, + + _addPersistNodeRef (node) { + var deps = []; + visitNode(node, deps); + for (let i = 0, l = deps.length; i < l; i++) { + var dependAsset = assets.get(deps[i]); + if (dependAsset) { + dependAsset.addRef(); + } + } + _persistNodeDeps.add(node.uuid, deps); + }, + + _removePersistNodeRef (node) { + if (_persistNodeDeps.has(node.uuid)) { + var deps = _persistNodeDeps.get(node.uuid); + for (let i = 0, l = deps.length; i < l; i++) { + var dependAsset = assets.get(deps[i]); + if (dependAsset) { + dependAsset.decRef(); + } + } + _persistNodeDeps.remove(node.uuid); + } + }, + + // do auto release + _autoRelease (oldScene, newScene, persistNodes) { + + // transfer refs from persist nodes to new scene + for (let i = 0, l = persistNodes.length; i < l; i++) { + var node = persistNodes[i]; + var sceneDeps = dependUtil._depends.get(newScene._id); + var deps = _persistNodeDeps.get(node.uuid); + for (let i = 0, l = deps.length; i < l; i++) { + var dependAsset = assets.get(deps[i]); + if (dependAsset) { + dependAsset.addRef(); + } + } + if (sceneDeps) { + !sceneDeps.persistDeps && (sceneDeps.persistDeps = []); + sceneDeps.persistDeps.push.apply(sceneDeps.persistDeps, deps); + } + } + + if (oldScene) { + var childs = dependUtil.getDeps(oldScene._id); + for (let i = 0, l = childs.length; i < l; i++) { + let asset = assets.get(childs[i]); + asset && asset.decRef(CC_TEST || oldScene.autoReleaseAssets); + } + var dependencies = dependUtil._depends.get(oldScene._id); + if (dependencies && dependencies.persistDeps) { + var persistDeps = dependencies.persistDeps; + for (let i = 0, l = persistDeps.length; i < l; i++) { + let asset = assets.get(persistDeps[i]); + asset && asset.decRef(CC_TEST || oldScene.autoReleaseAssets); + } + } + dependUtil.remove(oldScene._id); + } + }, + + _free (asset, force) { + _toDelete.remove(asset._uuid); + + if (!cc.isValid(asset, true)) return; + + if (!force) { + if (asset.refCount > 0) { + if (checkCircularReference(asset) > 0) return; + } + } + + // remove from cache + assets.remove(asset._uuid); + var depends = dependUtil.getDeps(asset._uuid); + for (let i = 0, l = depends.length; i < l; i++) { + var dependAsset = assets.get(depends[i]); + if (dependAsset) { + dependAsset.decRef(false); + releaseManager._free(dependAsset, false); + } + } + asset.destroy(); + dependUtil.remove(asset._uuid); + }, + + tryRelease (asset, force) { + if (!(asset instanceof cc.Asset)) return; + if (force) { + releaseManager._free(asset, force); + } + else { + _toDelete.add(asset._uuid, asset); + if (!eventListener) { + eventListener = true; + cc.director.once(cc.Director.EVENT_AFTER_DRAW, freeAssets); + } + } + } +}; + +module.exports = releaseManager; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/request-item.js b/cocos2d/core/asset-manager/request-item.js new file mode 100644 index 00000000000..7810c80f57a --- /dev/null +++ b/cocos2d/core/asset-manager/request-item.js @@ -0,0 +1,232 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +/** + * @module cc.AssetManager + */ + +var MAX_DEAD_NUM = 500; +var _deadPool = []; + +/** + * !#en + * A collection of information about a request + * + * !#zh + * 请求的相关信息集合 + * + * @class RequestItem + */ +function RequestItem () { + + this._id = ''; + + /** + * !#en + * The uuid of request + * + * !#zh + * 请求资源的uuid + * + * @property uuid + * @type {String} + */ + this.uuid = ''; + + /** + * !#en + * The final url of request + * + * !#zh + * 请求的最终url + * + * @property url + * @type {String} + */ + this.url = ''; + + /** + * !#en + * The extension name of asset + * + * !#zh + * 资源的扩展名 + * + * @property ext + * @type {String} + */ + this.ext = '.json'; + + /** + * !#en + * The content of asset + * + * !#zh + * 资源的内容 + * + * @property content + * @type {*} + */ + this.content = null; + + /** + * !#en + * The file of asset + * + * !#zh + * 资源的文件 + * + * @property file + * @type {*} + */ + this.file = null; + + /** + * !#en + * The information of asset + * + * !#zh + * 资源的相关信息 + * + * @property info + * @type {Object} + */ + this.info = null; + + this.config = null; + + /** + * !#en + * Whether or not it is native asset + * + * !#zh + * 资源是否是原生资源 + * + * @property isNative + * @type {Boolean} + */ + this.isNative = false; + + /** + * !#en + * Custom options + * + * !#zh + * 自定义参数 + * + * @property options + * @type {Object} + */ + this.options = Object.create(null); +} + +RequestItem.prototype = { + + /** + * !#en + * Create a request item + * + * !#zh + * 创建一个 request item + * + * @method constructor + * + * @typescript + * constructor() + */ + constructor: RequestItem, + + /** + * !#en + * The id of request, combined from uuid and isNative + * + * !#zh + * 请求的 id, 由 uuid 和 isNative 组合而成 + * + * @property id + * @type {String} + */ + get id () { + if (!this._id) { + this._id = this.uuid + '@' + (this.isNative ? 'native' : 'import'); + } + return this._id; + }, + + /** + * !#en + * Recycle this for reuse + * + * !#zh + * 回收 requestItem 用于复用 + * + * @method recycle + * + * @typescript + * recycle(): void + */ + recycle () { + if (_deadPool.length === MAX_DEAD_NUM) return; + this._id = ''; + this.uuid = ''; + this.url = ''; + this.ext = '.json'; + this.content = null; + this.file = null; + this.info = null; + this.config = null; + this.isNative = false; + this.options = Object.create(null); + _deadPool.push(this); + } +}; + +/** + * !#en + * Create a new request item from pool + * + * !#zh + * 从对象池中创建 requestItem + * + * @static + * @method create + * @returns {RequestItem} requestItem + * + * @typescript + * create(): RequestItem + */ +RequestItem.create = function () { + var out = null; + if (_deadPool.length !== 0) { + out = _deadPool.pop(); + } + else { + out = new RequestItem(); + } + + return out; +}; + +module.exports = RequestItem; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/shared.js b/cocos2d/core/asset-manager/shared.js new file mode 100644 index 00000000000..ad5bbe49f5f --- /dev/null +++ b/cocos2d/core/asset-manager/shared.js @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const Cache = require('./cache'); +const Pipeline = require('./pipeline'); + +var assets = new Cache(); +var files = new Cache(); +var parsed = new Cache(); +var bundles = new Cache(); +var pipeline = new Pipeline('normal load', []); +var fetchPipeline = new Pipeline('fetch', []); +var transformPipeline = new Pipeline('transform url', []); + +/** + * @module cc.AssetManager + */ + +var RequestType = { + + UUID: 'uuid', + + PATH: 'path', + + DIR: 'dir', + + URL: 'url', + + SCENE: 'scene' +}; + +/** + * !#en + * The builtin bundles + * + * !#zh + * 内置 bundle + * + * @enum BuiltinBundleName + */ +var BuiltinBundleName = { + /** + * !#en + * The builtin bundle corresponds to 'assets/resources'. + * + * !#zh + * 内置 bundle, 对应 'assets/resources' 目录 + * + * @property RESOURCES + * @readonly + * @type {String} + */ + RESOURCES: 'resources', + + /** + * !#en + * The builtin bundle corresponds to 'internal/resources'. + * + * !#zh + * 内置 bundle, 对应 'internal/resources' 目录 + * + * @property INTERNAL + * @readonly + * @type {String} + */ + INTERNAL: 'internal', + + /** + * !#en + * The builtin bundle + * + * !#zh + * 内置 bundle + * + * @property MAIN + * @readonly + * @type {String} + */ + MAIN: 'main', + + /** + * !#en + * The builtin bundle, exists when Start Scene asset bundle is checked on the project building panel + * + * !#zh + * 内置 bundle, 如果构建面板开启了首场景分包,则会有 START_SCENE bundle + * + * @property START_SCENE + * @readonly + * @type {String} + */ + START_SCENE: 'start-scene', +}; + +module.exports = { assets, files, parsed, pipeline, fetchPipeline, transformPipeline, RequestType, bundles, BuiltinBundleName }; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/task.js b/cocos2d/core/asset-manager/task.js new file mode 100644 index 00000000000..c94d5d860f2 --- /dev/null +++ b/cocos2d/core/asset-manager/task.js @@ -0,0 +1,328 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +/** + * @module cc.AssetManager + */ + +var _taskId = 0; +var MAX_DEAD_NUM = 500; +var _deadPool = []; + +/** + * !#en + * Task is used to run in the pipeline for some effect + * + * !#zh + * 任务用于在管线中运行以达成某种效果 + * + * @class Task + */ +function Task (options) { + /** + * !#en + * The id of task + * + * !#zh + * 任务id + * + * @property id + * @type {Number} + */ + this.id = _taskId++; + + this._isFinish = true; + + /** + * !#en + * The callback when task is completed + * + * !#zh + * 完成回调 + * + * @property onComplete + * @type {Function} + */ + this.onComplete = null; + + /** + * !#en + * The callback of progression + * + * !#zh + * 进度回调 + * + * @property onProgress + * @type {Function} + */ + this.onProgress = null; + + /** + * !#en + * The callback when something goes wrong + * + * !#zh + * 错误回调 + * + * @property onError + * @type {Function} + */ + this.onError = null; + + /** + * !#en + * The source of task + * + * !#zh + * 任务的源 + * + * @property source + * @type {*} + */ + this.source = null; + + /** + * !#en + * The output of task + * + * !#zh + * 任务的输出 + * + * @property output + * @type {*} + */ + this.output = null + + /** + * !#en + * The input of task + * + * !#zh + * 任务的输入 + * + * @property input + * @type {*} + */ + this.input = null; + + /** + * !#en + * The progression of task + * + * !#zh + * 任务的进度 + * + * @property progress + * @type {*} + */ + this.progress = null; + + /** + * !#en + * Custom options + * + * !#zh + * 自定义参数 + * + * @property options + * @type {Object} + */ + this.options = null; + this.set(options); +}; + +Task.prototype = { + + /** + * !#en + * Create a new Task + * + * !#zh + * 创建一个任务 + * + * @method constructor + * @param {Object} [options] - Some optional paramters + * @param {Function} [options.onComplete] - Callback when the task is completed, if the pipeline is synchronous, onComplete is unnecessary. + * @param {Function} [options.onProgress] - Continuously callback when the task is runing, if the pipeline is synchronous, onProgress is unnecessary. + * @param {Function} [options.onError] - Callback when something goes wrong, if the pipeline is synchronous, onError is unnecessary. + * @param {*} options.input - Something will be handled with pipeline + * @param {*} [options.progress] - Progress information, you may need to assign it manually when multiple pipeline share one progress + * @param {Object} [options.options] - Custom parameters + * + * @typescript + * constructor(options?: {onComplete?: (err: Error, result: any) => void, onError?: () => void, onProgress?: Function, input: any, progress?: any, options?: Record}) + */ + constructor: Task, + + /** + * !#en + * Set paramters of this task + * + * !#zh + * 设置任务的参数 + * + * @method set + * @param {Object} [options] - Some optional paramters + * @param {Function} [options.onComplete] - Callback when the task complete, if the pipeline is synchronous, onComplete is unnecessary. + * @param {Function} [options.onProgress] - Continuously callback when the task is runing, if the pipeline is synchronous, onProgress is unnecessary. + * @param {Function} [options.onError] - Callback when something goes wrong, if the pipeline is synchronous, onError is unnecessary. + * @param {*} options.input - Something will be handled with pipeline + * @param {*} [options.progress] - Progress information, you may need to assign it manually when multiple pipeline share one progress + * @param {Object} [options.options] - Custom parameters + * + * @example + * var task = new Task(); + * task.set({input: ['test'], onComplete: (err, result) => console.log(err), onProgress: (finish, total) => console.log(finish / total)}); + * + * @typescript + * set(options?: {onComplete?: (err: Error, result: any) => void, onError?: () => void, onProgress?: Function, input: any, progress?: any, options?: Record}): void + */ + set (options) { + options = options || Object.create(null); + this.onComplete = options.onComplete; + this.onProgress = options.onProgress; + this.onError = options.onError; + this.source = this.input = options.input; + this.output = null; + this.progress = options.progress; + // custom data + this.options = options.options || Object.create(null); + }, + + /** + * !#en + * Dispatch event + * + * !#zh + * 发布事件 + * + * @method dispatch + * @param {string} event - The event name + * @param {*} param1 - Parameter 1 + * @param {*} param2 - Parameter 2 + * @param {*} param3 - Parameter 3 + * @param {*} param4 - Parameter 4 + * + * @example + * var task = Task.create(); + * Task.onComplete = (msg) => console.log(msg); + * Task.dispatch('complete', 'hello world'); + * + * @typescript + * dispatch(event: string, param1?: any, param2?: any, param3?: any, param4?: any): void + */ + dispatch (event, param1, param2, param3, param4) { + switch (event) { + case 'complete' : + this.onComplete && this.onComplete(param1, param2, param3, param4); + break; + case 'progress': + this.onProgress && this.onProgress(param1, param2, param3, param4); + break; + case 'error': + this.onError && this.onError(param1, param2, param3, param4); + break; + default: + var str = 'on' + event[0].toUpperCase() + event.substr(1); + if (typeof this[str] === 'function') { + this[str](param1, param2, param3, param4); + } + break; + } + }, + + /** + * !#en + * Recycle this for reuse + * + * !#zh + * 回收 task 用于复用 + * + * @method recycle + * + * @typescript + * recycle(): void + */ + recycle () { + if (_deadPool.length === MAX_DEAD_NUM) return; + this.onComplete = null; + this.onProgress = null; + this.onError = null; + this.source = this.output = this.input = null; + this.progress = null; + this.options = null; + _deadPool.push(this); + }, + + /** + * !#en + * Whether or not this task is completed + * + * !#zh + * 此任务是否已经完成 + * + * @property isFinish + * @type {Boolean} + */ + get isFinish () { + return this._isFinish; + } +}; + +/** + * !#en + * Create a new task from pool + * + * !#zh + * 从对象池中创建 task + * + * @static + * @method create + * @param {Object} [options] - Some optional paramters + * @param {Function} [options.onComplete] - Callback when the task complete, if the pipeline is synchronous, onComplete is unnecessary. + * @param {Function} [options.onProgress] - Continuously callback when the task is runing, if the pipeline is synchronous, onProgress is unnecessary. + * @param {Function} [options.onError] - Callback when something goes wrong, if the pipeline is synchronous, onError is unnecessary. + * @param {*} options.input - Something will be handled with pipeline + * @param {*} [options.progress] - Progress information, you may need to assign it manually when multiple pipeline share one progress + * @param {Object} [options.options] - Custom parameters + * @returns {Task} task + * + * @typescript + * create(options?: {onComplete?: (err: Error, result: any) => void, onError?: () => void, onProgress?: Function, input: any, progress?: any, options?: Record}): Task + */ +Task.create = function (options) { + var out = null; + if (_deadPool.length !== 0) { + out = _deadPool.pop(); + out.set(options); + } + else { + out = new Task(options); + } + + return out; +}; + +module.exports = Task; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/urlTransformer.js b/cocos2d/core/asset-manager/urlTransformer.js new file mode 100644 index 00000000000..16f324a84fe --- /dev/null +++ b/cocos2d/core/asset-manager/urlTransformer.js @@ -0,0 +1,183 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const { decodeUuid } = require('./helper'); +const RequestItem = require('./request-item'); +const { RequestType, bundles } = require('./shared'); + +function parse (task) { + + var input = task.input, options = task.options; + input = Array.isArray(input) ? input : [ input ]; + + task.output = []; + for (var i = 0; i < input.length; i ++ ) { + var item = input[i]; + var out = RequestItem.create(); + if (typeof item === 'string') { + item = Object.create(null); + item[options.__requestType__ || RequestType.UUID] = input[i]; + } + if (typeof item === 'object') { + // local options will overlap glabal options + cc.js.addon(item, options); + if (item.preset) { + cc.js.addon(item, cc.assetManager.presets[item.preset]); + } + for (var key in item) { + switch (key) { + case RequestType.UUID: + var uuid = out.uuid = decodeUuid(item.uuid); + if (bundles.has(item.bundle)) { + var config = bundles.get(item.bundle)._config; + var info = config.getAssetInfo(uuid); + if (info && info.redirect) { + if (!bundles.has(info.redirect)) throw new Error(`Please load bundle ${info.redirect} first`); + config = bundles.get(info.redirect)._config; + info = config.getAssetInfo(uuid); + } + out.config = config; + out.info = info; + } + out.ext = item.ext || '.json'; + break; + case '__requestType__': + case 'ext': + case 'bundle': + case 'preset': + case 'type': break; + case RequestType.DIR: + if (bundles.has(item.bundle)) { + var infos = []; + bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos); + for (let i = 0, l = infos.length; i < l; i++) { + var info = infos[i]; + input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle}); + } + } + out.recycle(); + out = null; + break; + case RequestType.PATH: + if (bundles.has(item.bundle)) { + var config = bundles.get(item.bundle)._config; + var info = config.getInfoWithPath(item.path, item.type); + + if (info && info.redirect) { + if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`); + config = bundles.get(info.redirect)._config; + info = config.getAssetInfo(info.uuid); + } + + if (!info) { + out.recycle(); + throw new Error(`Bundle ${item.bundle} doesn't contain ${item.path}`); + } + out.config = config; + out.uuid = info.uuid; + out.info = info; + } + out.ext = item.ext || '.json'; + break; + case RequestType.SCENE: + if (bundles.has(item.bundle)) { + var config = bundles.get(item.bundle)._config; + var info = config.getSceneInfo(item.scene); + + if (info && info.redirect) { + if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`); + config = bundles.get(info.redirect)._config; + info = config.getAssetInfo(info.uuid); + } + if (!info) { + out.recycle(); + throw new Error(`Bundle ${config.name} doesn't contain scene ${item.scene}`); + } + out.config = config; + out.uuid = info.uuid; + out.info = info; + } + break; + case '__isNative__': + out.isNative = item.__isNative__; + break; + case RequestType.URL: + out.url = item.url; + out.uuid = item.uuid || item.url; + out.ext = item.ext || cc.path.extname(item.url); + out.isNative = item.__isNative__ !== undefined ? item.__isNative__ : true; + break; + default: out.options[key] = item[key]; + } + if (!out) break; + } + } + if (!out) continue; + task.output.push(out); + if (!out.uuid && !out.url) throw new Error('unknown input:' + item.toString()); + } + return null; +} + +function combine (task) { + var input = task.output = task.input; + for (var i = 0; i < input.length; i++) { + var item = input[i]; + if (item.url) continue; + + var url = '', base = ''; + var config = item.config; + if (item.isNative) { + base = (config && config.nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase; + } + else { + base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase; + } + + let uuid = item.uuid; + + var ver = ''; + if (item.info) { + if (item.isNative) { + ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : ''; + } + else { + ver = item.info.ver ? ('.' + item.info.ver) : ''; + } + } + + // ugly hack, WeChat does not support loading font likes 'myfont.dw213.ttf'. So append hash to directory + if (item.ext === '.ttf') { + url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`; + } + else { + url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`; + } + + item.url = url; + } + return null; +} + +module.exports = { parse, combine }; \ No newline at end of file diff --git a/cocos2d/core/asset-manager/utilities.js b/cocos2d/core/asset-manager/utilities.js new file mode 100644 index 00000000000..08f62dc71e7 --- /dev/null +++ b/cocos2d/core/asset-manager/utilities.js @@ -0,0 +1,340 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const dependUtil = require('./depend-util'); +const { isScene, decodeUuid } = require('./helper'); +const { assets } = require('./shared'); +const { callInNextTick } = require('../platform/utils'); +const MissingObjectReporter = CC_EDITOR && Editor.require('app://editor/page/scene-utils/missing-object-reporter'); +require('../assets/CCAsset'); + +var utils = { + + processOptions (options) { + if (CC_EDITOR) return; + var uuids = options.uuids; + var paths = options.paths; + var types = options.types; + var bundles = options.deps; + var realEntries = options.paths = Object.create(null); + + if (options.debug === false) { + for (let i = 0, l = uuids.length; i < l; i++) { + uuids[i] = decodeUuid(uuids[i]); + } + + for (let id in paths) { + let entry = paths[id]; + let type = entry[1]; + entry[1] = types[type]; + } + } + else { + var out = Object.create(null); + for (let i = 0, l = uuids.length; i < l; i++) { + let uuid = uuids[i]; + uuids[i] = out[uuid] = decodeUuid(uuid); + } + uuids = out; + } + + for (let id in paths) { + let entry = paths[id]; + realEntries[uuids[id]] = entry; + } + + var scenes = options.scenes; + for (let name in scenes) { + let uuid = scenes[name]; + scenes[name] = uuids[uuid]; + } + + var packs = options.packs; + for (let packId in packs) { + let packedIds = packs[packId]; + for (let j = 0; j < packedIds.length; ++j) { + packedIds[j] = uuids[packedIds[j]]; + } + } + + var versions = options.versions; + if (versions) { + for (let folder in versions) { + var entries = versions[folder]; + for (let i = 0; i < entries.length; i += 2) { + let uuid = entries[i]; + entries[i] = uuids[uuid] || uuid; + } + } + } + + var redirect = options.redirect; + if (redirect) { + for (let i = 0; i < redirect.length; i += 2) { + redirect[i] = uuids[redirect[i]]; + redirect[i + 1] = bundles[redirect[i + 1]]; + } + } + + }, + + clear (task, clearRef) { + for (var i = 0, l = task.input.length; i < l; i++) { + var item = task.input[i]; + if (clearRef) { + !item.isNative && item.content && item.content.decRef && item.content.decRef(false); + } + item.recycle(); + } + task.input = null; + }, + + urlAppendTimestamp (url) { + if (cc.assetManager.downloader.appendTimeStamp && typeof url === 'string') { + if (/\?/.test(url)) + return url + '&_t=' + (new Date() - 0); + else + return url + '?_t=' + (new Date() - 0); + } + return url; + }, + + retry (process, times, wait, onComplete, index) { + index = index || 0; + process(index, function (err, result) { + index++; + if (!err || index > times) { + onComplete && onComplete(err, result); + } + else { + setTimeout(function () { + utils.retry(process, times, wait, onComplete, index); + }, wait); + } + }); + }, + + getDepends (uuid, data, exclude, depends, preload, asyncLoadAssets, config) { + try { + var info = dependUtil.parse(uuid, data); + var includeNative = true; + if (data instanceof cc.Asset && (!data.__nativeDepend__ || data._nativeAsset)) includeNative = false; + if (!preload) { + asyncLoadAssets = !CC_EDITOR && (!!data.asyncLoadAssets || (asyncLoadAssets && !info.preventDeferredLoadDependents)); + for (let i = 0, l = info.deps.length; i < l; i++) { + let dep = info.deps[i]; + if (!(dep in exclude)) { + exclude[dep] = true; + depends.push({uuid: dep, __asyncLoadAssets__: asyncLoadAssets, bundle: config && config.name}); + } + } + + if (includeNative && !asyncLoadAssets && !info.preventPreloadNativeObject && info.nativeDep) { + config && (info.nativeDep.bundle = config.name); + depends.push(Object.assign({}, info.nativeDep)); + } + + } else { + for (let i = 0, l = info.deps.length; i < l; i++) { + let dep = info.deps[i]; + if (!(dep in exclude)) { + exclude[dep] = true; + depends.push({uuid: dep, bundle: config && config.name}); + } + } + if (includeNative && info.nativeDep) { + config && (info.nativeDep.bundle = config.name); + depends.push(Object.assign({}, info.nativeDep)); + } + } + } + catch (e) { + cc.error(e.message, e.stack); + } + }, + + cache (id, asset, cacheAsset) { + if (!asset) return; + var _isScene = isScene(asset); + if (!_isScene && cacheAsset) { + assets.add(id, asset); + } + if (_isScene) { + if (CC_EDITOR && !asset.scene) { + Editor.error('Sorry, the scene data of "%s" is corrupted!', asset._uuid); + } + } + }, + + setProperties (uuid, asset, assetsMap) { + + var missingAsset = false; + let depends = asset.__depends__; + if (depends) { + var missingAssetReporter = null; + for (var i = 0, l = depends.length; i < l; i++) { + var depend = depends[i]; + var dependAsset = assetsMap[depend.uuid + '@import']; + if (!dependAsset) { + if (CC_EDITOR) { + !missingAssetReporter && (missingAssetReporter = new MissingObjectReporter(asset)); + missingAssetReporter.stashByOwner(depend.owner, depend.prop, Editor.serialize.asAsset(depend.uuid)); + } + else { + cc.error('The asset ' + depend.uuid + ' is missing!'); + } + missingAsset = true; + } + else { + depend.owner[depend.prop] = dependAsset.addRef(); + } + } + + missingAssetReporter && missingAssetReporter.reportByOwner(); + asset.__depends__ = undefined; + } + + if (asset.__nativeDepend__) { + if (!asset._nativeAsset) { + if (assetsMap[uuid + '@native']) { + asset._nativeAsset = assetsMap[uuid + '@native']; + } + else { + missingAsset = true; + if (CC_EDITOR) { + console.error(`the native asset of ${uuid} is missing!`); + } + } + } + asset.__nativeDepend__ = undefined; + } + return missingAsset; + }, + + gatherAsset (task) { + let source = task.source; + if (!task.options.__outputAsArray__ && source.length === 1) { + task.output = source[0].content; + } + else { + let output = task.output = []; + for (var i = 0, l = source.length; i < l; i++) { + output.push(source[i].content); + } + } + }, + + forEach (array, process, onComplete) { + var count = 0; + var errs = []; + if (array.length === 0) onComplete && onComplete(errs); + for (var i = 0, l = array.length; i < l; i++) { + process(array[i], function (err) { + if (err) { + errs.push(err); + } + count ++; + if (count === l) { + onComplete && onComplete(errs); + } + }); + } + }, + + parseParameters (options, onProgress, onComplete) { + if (onComplete === undefined) { + var isCallback = typeof options === 'function'; + if (onProgress) { + onComplete = onProgress; + if (!isCallback) { + onProgress = null; + } + } + else if (onProgress === undefined && isCallback) { + onComplete = options; + options = null; + onProgress = null; + } + if (onProgress !== undefined && isCallback) { + onProgress = options; + options = null; + } + } + options = options || Object.create(null); + return { options, onProgress, onComplete }; + }, + + parseLoadResArgs (type, onProgress, onComplete) { + if (onComplete === undefined) { + var isValidType = cc.js.isChildClassOf(type, cc.Asset); + if (onProgress) { + onComplete = onProgress; + if (isValidType) { + onProgress = null; + } + } + else if (onProgress === undefined && !isValidType) { + onComplete = type; + onProgress = null; + type = null; + } + if (onProgress !== undefined && !isValidType) { + onProgress = type; + type = null; + } + } + return { type, onProgress, onComplete }; + }, + + checkCircleReference (owner, uuid, map, checked) { + if (!checked) { + checked = Object.create(null); + } + let item = map[uuid]; + if (!item || checked[uuid]) { + return false; + } + checked[uuid] = true; + var result = false; + var deps = dependUtil.getDeps(uuid); + if (deps) { + for (var i = 0, l = deps.length; i < l; i++) { + var dep = deps[i]; + if (dep === owner || utils.checkCircleReference(owner, dep, map, checked)) { + result = true; + break; + } + } + } + return result; + }, + + asyncify (cb) { + return function (p1, p2) { + cb && callInNextTick(cb, p1, p2); + } + } +}; + +module.exports = utils; \ No newline at end of file diff --git a/cocos2d/core/assets/CCAsset.js b/cocos2d/core/assets/CCAsset.js index fceba18b02a..fabfd3844fe 100644 --- a/cocos2d/core/assets/CCAsset.js +++ b/cocos2d/core/assets/CCAsset.js @@ -2,7 +2,7 @@ Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -24,7 +24,7 @@ THE SOFTWARE. ****************************************************************************/ -var RawAsset = require('./CCRawAsset'); +var CCObject = require('../platform/CCObject'); /** * !#en @@ -45,22 +45,33 @@ var RawAsset = require('./CCRawAsset'); * - cc.Object._deserialize
* * @class Asset - * @extends RawAsset + * @extends Object */ cc.Asset = cc.Class({ - name: 'cc.Asset', extends: RawAsset, + name: 'cc.Asset', extends: CCObject, ctor () { + /** + * @property {String} _uuid + * @private + */ + // enumerable is false by default, to avoid uuid being assigned to empty string during destroy + Object.defineProperty(this, '_uuid', { + value: '', + writable: true, + }); /** * !#en - * Whether the asset is loaded or not + * Whether the asset is loaded or not. * !#zh - * 该资源是否已经成功加载 + * 该资源是否已经成功加载。 * * @property loaded * @type {Boolean} */ this.loaded = true; + this._nativeUrl = ''; + this._ref = 0; }, properties: { @@ -75,35 +86,50 @@ cc.Asset = cc.Class({ */ nativeUrl: { get: function () { - if (this._native) { - var name = this._native; - if (name.charCodeAt(0) === 47) { // '/' - // remove library tag - // not imported in library, just created on-the-fly - return name.slice(1); - } - if (cc.AssetLibrary) { - var base = cc.AssetLibrary.getLibUrlNoExt(this._uuid, true); + if (!this._nativeUrl) { + if (this._native) { + var name = this._native; + if (name.charCodeAt(0) === 47) { // '/' + // remove library tag + // not imported in library, just created on-the-fly + return name.slice(1); + } if (name.charCodeAt(0) === 46) { // '.' - // imported in dir where json exist - return base + name; + // imported in dir where json exist + this._nativeUrl = cc.assetManager.utils.getUrlWithUuid(this._uuid, {nativeExt: name, isNative: true }); } else { // imported in an independent dir - return base + '/' + name; + this._nativeUrl = cc.assetManager.utils.getUrlWithUuid(this._uuid, {__nativeName__: name, nativeExt: cc.path.extname(name), isNative: true}); } } - else { - cc.errorID(6400); - } } - return ''; + return this._nativeUrl; }, visible: false }, /** + * !#en + * The number of reference + * + * !#zh + * 引用的数量 + * + * @property refCount + * @type {Number} + */ + refCount: { + get () { + return this._ref; + } + }, + + /** + * !#en * Serializable url for native asset. + * !#zh + * 保存原生资源的 URL。 * @property {String} _native * @default undefined * @private @@ -111,22 +137,42 @@ cc.Asset = cc.Class({ _native: "", /** + * !#en * The underlying native asset of this asset if one is available. * This property can be used to access additional details or functionality releated to the asset. * This property will be initialized by the loader if `_native` is available. + * !#zh + * 此资源依赖的底层原生资源(如果有的话)。 + * 此属性可用于访问与资源相关的其他详细信息或功能。 + * 如果 `_native` 可用,则此属性将由加载器初始化。 * @property {Object} _nativeAsset * @default null * @private */ _nativeAsset: { - get () {}, - set (obj) {} + get () { + return this._$nativeAsset; + }, + set (obj) { + this._$nativeAsset = obj; + } }, + + _nativeDep: { + get () { + if (this._native) { + return {__isNative__: true, uuid: this._uuid, ext: this._native}; + } + } + } }, statics: { /** - * 应 AssetDB 要求提供这个方法 + * !#en + * Provide this method at the request of AssetDB. + * !#zh + * 应 AssetDB 要求提供这个方法。 * * @method deserialize * @param {String} data @@ -140,7 +186,7 @@ cc.Asset = cc.Class({ /** * !#en Indicates whether its dependent raw assets can support deferred load if the owner scene (or prefab) is marked as `asyncLoadAssets`. - * !#zh 当场景或 Prefab 被标记为 `asyncLoadAssets`,禁止延迟加载该资源所依赖的其它 RawAsset。 + * !#zh 当场景或 Prefab 被标记为 `asyncLoadAssets`,禁止延迟加载该资源所依赖的其它原始资源。 * * @property {Boolean} preventDeferredLoadDependents * @default false @@ -157,15 +203,22 @@ cc.Asset = cc.Class({ * @static */ preventPreloadNativeObject: false + }, /** + * !#en * Returns the asset's url. - * + * The `Asset` object overrides the `toString()` method of the `Object` object. - * For `Asset` objects, the toString() method returns a string representation of the object. - * JavaScript calls the toString() method automatically when an asset is to be represented as a text value or when a texture is referred to in a string concatenation. - * + * For `Asset` objects, the `toString()` method returns a string representation of the object. + * JavaScript calls the `toString()` method automatically when an asset is to be represented as a text value or when a texture is referred to in a string concatenation. + * !#zh + * 返回资源的 URL。 + * + * Asset 对象将会重写 Object 对象的 `toString()` 方法。 + * 对于 Asset 对象,`toString()` 方法返回该对象的字符串表示形式。 + * 当资源要表示为文本值时或在字符串连接时引用时,JavaScript 会自动调用 `toString()` 方法。 * @method toString * @return {String} */ @@ -174,7 +227,10 @@ cc.Asset = cc.Class({ }, /** - * 应 AssetDB 要求提供这个方法 + * !#en + * Provide this method at the request of AssetDB. + * !#zh + * 应 AssetDB 要求提供这个方法。 * * @method serialize * @return {String} @@ -200,7 +256,11 @@ cc.Asset = cc.Class({ createNode: null, /** + * !#en * Set native file name for this asset. + * !#zh + * 为此资源设置原生文件名。 + * * @seealso nativeUrl * * @method _setRawAsset @@ -215,6 +275,43 @@ cc.Asset = cc.Class({ else { this._native = '/' + filename; // simply use '/' to tag location where is not in the library } + }, + + /** + * !#en + * Add references of asset + * + * !#zh + * 增加资源的引用 + * + * @method addRef + * @return {Asset} itself + * + * @typescript + * addRef(): cc.Asset + */ + addRef () { + this._ref++; + return this; + }, + + /** + * !#en + * Reduce references of asset and it will be auto released when refCount equals 0. + * + * !#zh + * 减少资源的引用并尝试进行自动释放。 + * + * @method decRef + * @return {Asset} itself + * + * @typescript + * decRef(): cc.Asset + */ + decRef (autoRelease) { + this._ref--; + autoRelease !== false && cc.assetManager._releaseManager.tryRelease(this); + return this; } }); diff --git a/cocos2d/core/assets/CCAudioClip.js b/cocos2d/core/assets/CCAudioClip.js index 5bbbca5fbf8..033e19e5146 100644 --- a/cocos2d/core/assets/CCAudioClip.js +++ b/cocos2d/core/assets/CCAudioClip.js @@ -2,7 +2,7 @@ Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - http://www.cocos.com + https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated engine source code (the "Software"), a limited, @@ -45,6 +45,7 @@ var AudioClip = cc.Class({ mixins: [EventTarget], ctor () { + this._loading = false; this.loaded = false; // the web audio buffer or