初始化

This commit is contained in:
SmallMain
2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

View File

@@ -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 typecurrently 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;

View File

@@ -0,0 +1,90 @@
/****************************************************************************
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.
****************************************************************************/
let Model = cc.Class({
name: 'cc.Model',
extends: cc.Asset,
ctor () {
this._rootNode = null;
this.loaded = false;
},
properties: {
_nodes: {
default: []
},
_precomputeJointMatrix: false,
nodes: {
get () {
return this._nodes;
}
},
rootNode: {
get () {
return this._rootNode;
}
},
precomputeJointMatrix: {
get () {
return this._precomputeJointMatrix;
}
}
},
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);
if (node.uniqueBindPose) {
node.uniqueBindPose = cc.mat4.apply(this, node.uniqueBindPose);
}
let pose = node.bindpose;
if (pose) {
for (let i in pose) {
pose[i] = cc.mat4.apply(this, pose[i]);
}
}
let children = node.children;
if (children) {
for (let i = 0; i < children.length; i++) {
children[i] = nodes[children[i]];
}
}
}
}
});
cc.Model = module.exports = Model;

View File

@@ -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. <br/>
* 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. <br/>
* 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);
};

View File

@@ -0,0 +1,22 @@
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('./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');
}

View File

@@ -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)));
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,161 @@
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],
visible: function (this) {
return !this.separateAxes;
}
})
limit = new CurveRange();
/**
* !#en Lower speed limit in X direction.
* !#zh X 轴方向上的速度下限。
* @property {CurveRange} limitX
*/
@property({
type: CurveRange,
range: [-1, 1],
visible: function (this) {
return this.separateAxes;
}
})
limitX = new CurveRange();
/**
* !#en Lower speed limit in Y direction.
* !#zh Y 轴方向上的速度下限。
* @property {CurveRange} limitY
*/
@property({
type: CurveRange,
range: [-1, 1],
visible: function (this) {
return this.separateAxes;
}
})
limitY = new CurveRange();
/**
* !#en Lower speed limit in Z direction.
* !#zh Z 轴方向上的速度下限。
* @property {CurveRange} limitZ
*/
@property({
type: CurveRange,
range: [-1, 1],
visible: function (this) {
return this.separateAxes;
}
})
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);
}
}

View File

@@ -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.unshift(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];
}
}
}

View File

@@ -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) {
this._separateAxes = val;
}
/**
* !#en Set rotation around X axis.
* !#zh 绕 X 轴设定旋转。
* @property {CurveRange} x
*/
@property({
type: CurveRange,
range: [-1, 1],
radian: true,
visible: function (this) {
return this._separateAxes;
}
})
x = new CurveRange();
/**
* !#en Set rotation around Y axis.
* !#zh 绕 Y 轴设定旋转。
* @property {CurveRange} y
*/
@property({
type: CurveRange,
range: [-1, 1],
radian: true,
visible: function (this) {
return this._separateAxes;
}
})
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;
}
}
}

View File

@@ -0,0 +1,95 @@
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,
visible: function (this) {
return !this.separateAxes;
}
})
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,
visible: function (this) {
return this.separateAxes;
}
})
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,
visible: function (this) {
return this.separateAxes;
}
})
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,
visible: function (this) {
return this.separateAxes;
}
})
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);
}
}
}

View File

@@ -0,0 +1,245 @@
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;
}
}
@property
_numTilesX = 0;
/**
* !#en Animation frames in X direction.
* !#zh X 方向动画帧数。
* @property {Number} numTilesX
*/
@property
get numTilesX () {
return this._numTilesX;
};
set numTilesX (val) {
if (this._numTilesX === val) return;
this._numTilesX = val;
if (this.ps && this.ps._assembler) this.ps._assembler._updateMaterialParams();
}
@property
_numTilesY = 0;
/**
* !#en Animation frames in Y direction.
* !#zh Y 方向动画帧数。
* @property {Number} numTilesY
*/
@property
get numTilesY () {
return this._numTilesY;
}
set numTilesY (val) {
if (this._numTilesY === val) return;
this._numTilesY = val;
if (this.ps && this.ps._assembler) this.ps._assembler._updateMaterialParams();
}
/**
* !#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. <br>
     * This option only takes effect when the animation playback mode is SingleRow.
* !#zh 随机从动画贴图中选择一行以生成动画。<br>
* 此选项仅在动画播放方式为 SingleRow 时生效。
* @property {Boolean} randomRow
*/
@property
randomRow = false;
/**
* !#en Select specific lines from the animation map to generate the animation. <br>
     * This option is only available when the animation playback mode is SingleRow and randomRow is disabled.
* !#zh 从动画贴图中选择特定行以生成动画。<br>
* 此选项仅在动画播放方式为 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));
}
}
}
}

View File

@@ -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)));
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,311 @@
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.ClampForever;
/**
* !#en Post-wrap mode.
* !#zh 后置循环模式。
* @property {WrapMode} postWrapMode
*/
@property({
type: cc.Enum(WrapMode),
visible: false,
})
postWrapMode = WrapMode.ClampForever;
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 (!CC_EDITOR) {
if (wrappedTime >= this.cachedKey.time && wrappedTime < this.cachedKey.endTime) {
return this.cachedKey.evaluate(wrappedTime);
}
}
const leftIndex = this.findIndex(this.cachedKey, wrappedTime);
const rightIndex = Math.min(leftIndex + 1, this.keyFrames!.length - 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 - 1) >= 0 && this.keyFrames![currIndex - 1].time <= t) {
return currIndex - 1;
}
}
}
}
let left = 0;
let right = this.keyFrames!.length;
let mid;
while (right - left > 1) {
mid = Math.floor((left + right) / 2);
if (this.keyFrames![mid].time >= t) {
right = mid;
} else {
left = mid;
}
}
return left;
}
}
cc.Keyframe = Keyframe;
cc.AnimationCurve = AnimationCurve;

View File

@@ -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)<bg>
* - 0 means emitted from the surface;
     * - 1 means launch from the center;
     * - 0 ~ 1 indicates emission from the center to the surface.
* !#zh 粒子发射器发射位置(对 Box 类型的发射器无效):<bg>
* - 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<bg>
* Determines the opening and closing of the cone launcher
* !#zh 圆锥的轴与母线的夹角<bg>。
* 决定圆锥发射器的开合程度。
* @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 <bg>.
     * Determines the height of the cone emitter.
* !#zh 圆锥顶部截面距离底部的轴长<bg>。
* 决定圆锥发射器的高度。
* @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);
}
}

View File

@@ -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({
/**
* 粒子模式<bg>
* 创建一种效果,其中每个粒子在其路径中留下固定的轨迹
*/
Particles: 0,
/**
* 带模式<bg>
* 根据其生命周期创建连接每个粒子的轨迹带
*/
Ribbon: 1,
});
/**
* 纹理填充模式
* @enum trailModule.TextureMode
*/
export const TextureMode = Enum({
/**
* 拉伸填充纹理
*/
Stretch: 0,
/**
* 重复填充纹理
*/
Repeat: 1,
});

View File

@@ -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]]);
}
}
}
// FisherYates 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);
}

View File

@@ -0,0 +1,970 @@
/****************************************************************************
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.<br>
* World coordinates (does not change when the position of other objects changes)<br>
* Local coordinates (moving as the position of the parent node changes)<br>
* Custom coordinates (moving with the position of a custom node)
* !#zh 选择粒子系统所在的坐标系<br>
* 世界坐标(不随其他物体位置改变而变换)<br>
* 局部坐标(跟随父节点位置改变而移动)<br>
* 自定坐标(跟随自定义节点的位置改变而移动)
* @property {Space} simulationSpace
*/
@property({
type: Space,
animatable: false
})
get simulationSpace () {
return this._simulationSpace;
}
set simulationSpace (val) {
if (val !== this._simulationSpace) {
this._simulationSpace = val;
if (this._assembler) {
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;
if (this._assembler) {
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 && 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 && 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 && 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 && this._assembler._onMaterialModified(index, material);
}
_onRebuildPSO (index, material) {
this._assembler && 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 && this._assembler.clear();
this.trailModule.clear();
}
}
getParticleCount () {
return this._assembler ? this._assembler.getParticleCount() : 0;
}
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._assembler) {
return;
}
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 && 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;

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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'; // <wireframe debug>
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, // <wireframe debug>
};
}
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); // <wireframe debug>
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);

View File

@@ -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]; // <wireframe debug>
// 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 }, // <wireframe debug>
{ 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<Particle, TrailSegment>();
}
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; // <wireframe debug>
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++]; // <wireframe debug>
// 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++]; // <wireframe debug>
// 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;
}
}
}

View File

@@ -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
};
}
};
};
};

View File

@@ -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;
}
}

View File

@@ -0,0 +1,262 @@
/****************************************************************************
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, worldDirty } 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'
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<string, CannonSharedBody>();
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 = worldDirty(node);
if (!force && !needUpdateTransform) {
return;
}
// body world aabb need to be recalculated
this.body.aabbNeedsUpdate = true;
node.getWorldPosition(v3_0);
node.getWorldRotation(quat_0)
Vec3.copy(this.body.position, v3_0);
Quat.copy(this.body.quaternion, quat_0);
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);
this.node.setWorldPosition(v3_0);
this.node.setWorldRotation(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<CannonShape>(event.selfShape);
const other = getWrap<CannonShape>(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);
}
}
}
}

View File

@@ -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<IBaseShape>(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',
};

View File

@@ -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
}

View File

@@ -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,
);
}

View File

@@ -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 { 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);
v3_0.x = Math.abs(v3_0.x);
v3_0.y = Math.abs(v3_0.y);
v3_0.z = Math.abs(v3_0.z);
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;
}
}

View File

@@ -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<CannonShape>(event.selfShape);
const other = getWrap<CannonShape>(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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,147 @@
/****************************************************************************
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 { worldDirty } from "../framework/util"
const intersect = cc.geomUtils.intersect;
const fastRemove = cc.js.array.fastRemove;
const v3_0 = new cc.Vec3();
const v3_1 = new cc.Vec3();
const quat_0 = new cc.Quat();
/**
* Built-in static collider, no physical forces involved
*/
export class BuiltinSharedBody {
private static readonly sharedBodiesMap = new Map<string, BuiltinSharedBody>();
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 = worldDirty(node);
if (!force && !needUpdateTransform) return;
node.getWorldPosition(v3_0);
node.getWorldRotation(quat_0)
node.getWorldScale(v3_1);
for (let i = 0; i < this.shapes.length; i++) {
this.shapes[i].transform(node._worldMatrix, v3_0, quat_0, v3_1);
}
}
private destory () {
BuiltinSharedBody.sharedBodiesMap.delete(this.node._id);
(this.node as any) = null;
(this.world as any) = null;
(this.shapes as any) = null;
}
}

View File

@@ -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();
}
}

View File

@@ -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,
);
}

View File

@@ -0,0 +1,70 @@
/****************************************************************************
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);
_worldScale.x = Math.abs(_worldScale.x);
_worldScale.y = Math.abs(_worldScale.y);
_worldScale.z = Math.abs(_worldScale.z);
Vec3.multiply(this.worldObb.halfExtents, this.localObb.halfExtents, _worldScale);
}
onLoad () {
super.onLoad();
this.size = this.boxCollider.size;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
****************************************************************************/
import '../cocos/instantiate';

View File

@@ -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;

View File

@@ -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';

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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<T extends Function>(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);

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,192 @@
/****************************************************************************
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<T> {
__cc_wrapper__: T;
}
export function setWrap<Wrapper> (object: any, wrapper: Wrapper) {
(object as IWrapped<Wrapper>).__cc_wrapper__ = wrapper;
}
export function getWrap<Wrapper> (object: any) {
return (object as IWrapped<Wrapper>).__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<cc.Node> = [];
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);
}
}
export function worldDirty (node: cc.Node) {
let cur = node;
while (cur) {
if (cur._worldMatDirty) return true;
cur = cur._parent;
}
return false;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
);
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
*/

View File

@@ -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
);
}

View File

@@ -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
);
};

View File

@@ -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
);
}

View File

@@ -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
);
}

View File

@@ -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
);
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -0,0 +1,84 @@
/****************************************************************************
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.
****************************************************************************/
let Skeleton = cc.Class({
name: 'cc.Skeleton',
extends: cc.Asset,
ctor () {
this.loaded = false;
this._bindposes = [];
this._uniqueBindPoses = [];
this._jointPaths = [];
},
properties: {
_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;
}
}
},
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.Skeleton = module.exports = Skeleton;

View File

@@ -0,0 +1,140 @@
/****************************************************************************
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 Animation = require('../../components/CCAnimation');
const Model = require('../CCModel');
const SkeletonAnimationClip = require('./CCSkeletonAnimationClip');
/**
* @module cc
*/
/**
* !#en .
* !#zh 。
* @class SkeletonAnimation
* @extends Animation
*/
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: {
default: null,
type: Model
},
_defaultClip: {
override: true,
default: null,
type: SkeletonAnimationClip,
},
_clips: {
override: true,
default: [],
type: [SkeletonAnimationClip],
visible: true,
},
defaultClip: {
override: true,
get () {
return this._defaultClip;
},
set (v) {
this._defaultClip = v;
},
type: SkeletonAnimationClip,
},
model: {
get () {
return this._model;
},
set (val) {
this._model = val;
this._updateClipModel();
},
type: Model,
},
},
__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) {
if (err) return console.error(err);
const Path = require('fire-path');
let queryPath = Path.relative(Editor.remote.Project.path, modelPath);
queryPath = Path.join(Path.dirname(queryPath), Path.basenameNoExt(queryPath));
queryPath = `db://${queryPath}*/*.sac`;
Editor.assetdb.queryAssets(queryPath, null, function (err, results) {
if (results) {
for (let i = 0; i < results.length; i++) {
let clip = new SkeletonAnimationClip();
clip._uuid = results[i].uuid;
self._clips.push(clip);
}
self._defaultClip = self._clips[0];
}
});
});
}
});
cc.SkeletonAnimation = module.exports = SkeletonAnimation;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,37 @@
/****************************************************************************
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 SkinnedMeshRenderer = require('./CCSkinnedMeshRenderer');
const MeshRendererAssembler = require('../../mesh/mesh-renderer');
const RenderFlow = require('../../renderer/render-flow');
export default class SkinnedMeshRendererAssembler extends MeshRendererAssembler {
fillBuffers (comp, renderer) {
comp.calcJointMatrix();
super.fillBuffers(comp, renderer);
}
}
cc.Assembler.register(SkinnedMeshRenderer, SkinnedMeshRendererAssembler);