[add] first
This commit is contained in:
229
assets/scripts/core/ik/aim-control.ts
Normal file
229
assets/scripts/core/ik/aim-control.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { _decorator, Component, Node, Vec3, Quat, director, math, game } from 'cc';
|
||||
import MathUtil from './math-util';
|
||||
import AimIK from './aim-ik';
|
||||
import IKSolverAim from './ik-solver-aim';
|
||||
import { UtilVec3 } from '../util/util';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
let _tempVec3 = new Vec3;
|
||||
let _tempVec3_2 = new Vec3;
|
||||
let _tempQuat = new Quat;
|
||||
let _tempQuat_2 = new Quat;
|
||||
let tempPivot = new Vec3;
|
||||
|
||||
@ccclass('AimControl')
|
||||
export default class AimControl extends Component {
|
||||
@property({ type: AimIK, tooltip: 'Reference to the AimIK component.' })
|
||||
public ik: AimIK | null = null;
|
||||
|
||||
@property({ tooltip: 'Master weight of the IK solver.' })
|
||||
public weight: number = 1.0;
|
||||
|
||||
@property({ type: Node, tooltip: 'The target to aim at. Do not use the Target transform that is assigned to AimIK. Set to null if you wish to stop aiming.' })
|
||||
public target: Node | null = null;
|
||||
|
||||
@property({ tooltip: 'The time it takes to switch targets.' })
|
||||
public targetSwitchSmoothTime: number = 0.3;
|
||||
|
||||
@property({ tooltip: 'The time it takes to blend in/out of AimIK weight.' })
|
||||
public weightSmoothTime: number = 0.3;
|
||||
|
||||
@property({ tooltip: 'Enables smooth turning towards the target according to the parameters under this header.' })
|
||||
public smoothTurnTowardsTarget: boolean = true;
|
||||
|
||||
@property({ tooltip: 'Speed of turning towards the target using Vector3.RotateTowards.' })
|
||||
public maxRadiansDelta: number = 3;
|
||||
|
||||
@property({ tooltip: 'Speed of moving towards the target using Vector3.RotateTowards.' })
|
||||
public maxMagnitudeDelta: number = 3;
|
||||
|
||||
@property({ tooltip: 'Speed of slerping towards the target.' })
|
||||
public slerpSpeed: number = 3.0;
|
||||
|
||||
@property({ tooltip: 'The position of the pivot that the aim target is rotated around relative to the root of the character.' })
|
||||
public pivotOffsetFromRoot: Vec3 = new Vec3(0, 0, 0);
|
||||
|
||||
@property({ tooltip: 'Minimum distance of aiming from the first bone. Keeps the solver from failing if the target is too close.' })
|
||||
public minDistance: number = 1.0;
|
||||
|
||||
@property({ tooltip: 'Offset applied to the target in world space. Convenient for scripting aiming inaccuracy.' })
|
||||
public offset: Vec3 = new Vec3(0, 0, 0);
|
||||
|
||||
@property({ tooltip: 'Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.' })
|
||||
public maxRootAngle: number = 45.0;
|
||||
|
||||
@property({ tooltip: 'If enabled, aligns the root forward to target direction after Max Root Angle has been exceeded.' })
|
||||
public turnToTarget: boolean = true;
|
||||
|
||||
@property({ tooltip: 'The time of turning towards the target direction if Max Root Angle has been exceeded and Turn To Target is enabled.' })
|
||||
public turnToTargetTime: number = 0.2;
|
||||
|
||||
@property({ tooltip: 'If true, AimIK will consider whatever the current direction of the weapon to be the forward aiming direction and work additively on top of that. This enables you to use recoil and reloading animations seamlessly with AimIK. Adjust the Vector3 value below if the weapon is not aiming perfectly forward in the aiming animation clip.' })
|
||||
public useAnimatedAimDirection: boolean = false;
|
||||
|
||||
@property({ tooltip: 'The direction of the animated weapon aiming in character space. Tweak this value to adjust the aiming. Use Animated Aim Direction must be enabled for this property to work.' })
|
||||
public animatedAimDirection: Vec3 = new Vec3(0, 0, 1);
|
||||
|
||||
private _lastTarget: Node | null = null;
|
||||
private _switchWeight: number = 0;
|
||||
private _switchWeightV: number = 0;
|
||||
private _weightV: number = 0;
|
||||
private _lastPosition: Vec3 = new Vec3();
|
||||
private _dir: Vec3 = new Vec3();
|
||||
private _lastSmoothTowardsTarget: boolean = false;
|
||||
|
||||
private turningToTarget: boolean = false;
|
||||
private turnToTargetMlp: number = 1.0;
|
||||
private turnToTargetMlpV: number = 0.0;
|
||||
private _interval: any;
|
||||
|
||||
start () {
|
||||
this._lastPosition = Vec3.clone(this.ik!.solver.ikPosition);
|
||||
Vec3.subtract(this._dir, this.ik!.solver.ikPosition, this._getPivot());
|
||||
this.ik!.solver.target = this.target;
|
||||
}
|
||||
|
||||
lateUpdate () {
|
||||
// If target has changed...
|
||||
if (this.target != this._lastTarget) {
|
||||
if (this._lastTarget == null && this.target != null && this.ik!.solver.ikPositionWeight <= 0.0) {
|
||||
this._lastPosition = Vec3.clone(this.target.getWorldPosition());
|
||||
Vec3.subtract(this._dir, this.target.getWorldPosition(), this._getPivot());
|
||||
Vec3.add(this.ik!.solver.ikPosition, this.target.getWorldPosition(), this.offset!);
|
||||
}
|
||||
else {
|
||||
this._lastPosition = Vec3.clone(this.ik!.solver.ikPosition);
|
||||
Vec3.subtract(this._dir, this.ik!.solver.ikPosition, this._getPivot());
|
||||
}
|
||||
this._switchWeight = 0.0;
|
||||
this._lastTarget = this.target;
|
||||
}
|
||||
|
||||
// Smooth weight
|
||||
let outValue = { value: this._weightV };
|
||||
this.ik!.solver.ikPositionWeight = MathUtil.smoothDamp(this.ik!.solver.ikPositionWeight, (this.target != null ? this.weight : 0), outValue, this.weightSmoothTime);
|
||||
this._weightV = outValue.value;
|
||||
|
||||
if (this.ik!.solver.ikPositionWeight >= 0.999) this.ik!.solver.ikPositionWeight = 1.0;
|
||||
if (this.ik!.solver.ikPositionWeight <= 0.001) this.ik!.solver.ikPositionWeight = 0.0;
|
||||
|
||||
if (this.ik!.solver.ikPositionWeight <= 0.0) return;
|
||||
// Smooth target switching
|
||||
outValue.value = this._switchWeightV;
|
||||
this._switchWeight = MathUtil.smoothDamp(this._switchWeight, 1.0, outValue, this.targetSwitchSmoothTime);
|
||||
this._switchWeightV = outValue.value;
|
||||
if (this._switchWeight >= 0.99) this._switchWeight = 1.0;
|
||||
|
||||
if (this.target != null) {
|
||||
Vec3.add(_tempVec3, this.target.getWorldPosition(), this.offset!);
|
||||
Vec3.lerp(this.ik!.solver.ikPosition, this._lastPosition, _tempVec3, this._switchWeight);
|
||||
}
|
||||
|
||||
// Smooth turn towards target
|
||||
if (this.smoothTurnTowardsTarget != this._lastSmoothTowardsTarget) {
|
||||
Vec3.subtract(this._dir, this.ik!.solver.ikPosition, this._getPivot());
|
||||
this._lastSmoothTowardsTarget = this.smoothTurnTowardsTarget;
|
||||
}
|
||||
|
||||
// fromViewUp
|
||||
if (this.smoothTurnTowardsTarget) {
|
||||
// Vector3 targetDir = ik.solver.ikPosition - pivot;
|
||||
// dir = Vector3.Slerp(dir, targetDir, Time.deltaTime * slerpSpeed);
|
||||
// dir = Vector3.RotateTowards(dir, targetDir, Time.deltaTime * maxRadiansDelta, maxMagnitudeDelta);
|
||||
// ik.solver.ikPosition = pivot + dir;
|
||||
// get targetDir
|
||||
Vec3.subtract(_tempVec3, this.ik!.solver.ikPosition, this._getPivot());
|
||||
Quat.fromViewUp(_tempQuat, _tempVec3, new Vec3(0, 1, 0));
|
||||
Quat.toEuler(this._dir, _tempQuat);
|
||||
Vec3.add(this.ik!.solver.ikPosition, this._getPivot(), this._dir);
|
||||
//console.log('ikPosition:',this.ik!.solver.ikPosition);
|
||||
}
|
||||
|
||||
// Min distance from the pivot
|
||||
this._applyMinDistance();
|
||||
// Root rotation
|
||||
this._rootRotation();
|
||||
|
||||
// Offset mode
|
||||
if (this.useAnimatedAimDirection) {
|
||||
Vec3.transformQuat(_tempVec3, this.animatedAimDirection, this.ik!.rootNode!.getWorldRotation());
|
||||
// MathUtil.directionToNodeSpace(this.ik!.solver.axis, _tempVec3, this.ik!.solver!.aimNode!);
|
||||
this.ik!.rootNode!.inverseTransformPoint(this.ik!.solver.axis, _tempVec3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_getPivot () {
|
||||
// this.ik!.transform.position + this.ik!.transform.rotation * this.pivotOffsetFromRoot;
|
||||
//let tempVec3 = new Vec3();
|
||||
Vec3.transformQuat(tempPivot, this.pivotOffsetFromRoot, this.ik!.rootNode!.getWorldRotation());
|
||||
Vec3.add(tempPivot, tempPivot, this.ik!.rootNode!.getWorldPosition());
|
||||
return tempPivot; //Vec3.clone(this.tempPivot);
|
||||
}
|
||||
|
||||
// Make sure aiming target is not too close (might make the solver instable when the target is closer to the first bone than the last bone is).
|
||||
_applyMinDistance () {
|
||||
//_tempVec3 = this._getPivot();
|
||||
UtilVec3.copy(_tempVec3, this._getPivot());
|
||||
Vec3.subtract(_tempVec3_2, this.ik!.solver.ikPosition, _tempVec3);
|
||||
let distance = Math.max(_tempVec3_2.length(), this.minDistance);
|
||||
_tempVec3_2.normalize();
|
||||
_tempVec3_2.multiplyScalar(distance);
|
||||
Vec3.add(this.ik!.solver.ikPosition, _tempVec3, _tempVec3_2);
|
||||
}
|
||||
|
||||
// Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.
|
||||
private _rootRotation () {
|
||||
let maxRootAngle = this.ik!.enableAim ? this.maxRootAngle : 0;
|
||||
let max = math.lerp(180.0, maxRootAngle * this.turnToTargetMlp, this.ik!.solver.ikPositionWeight);
|
||||
if (max < 180.0) {
|
||||
Quat.invert(_tempQuat, this.ik!.rootNode!.getWorldRotation());
|
||||
Vec3.subtract(_tempVec3, this.ik!.solver.ikPosition, this._getPivot());
|
||||
Vec3.transformQuat(_tempVec3, _tempVec3, _tempQuat);
|
||||
|
||||
let angle = math.toDegree(Math.atan2(_tempVec3.x, _tempVec3.z));
|
||||
|
||||
let rotation = 0.0;
|
||||
|
||||
if (angle > max) {
|
||||
rotation = angle - max;
|
||||
if (!this.turningToTarget && this.turnToTarget) this._startCoroutine(this._turnToTarget.bind(this));
|
||||
}
|
||||
if (angle < -max) {
|
||||
rotation = angle + max;
|
||||
if (!this.turningToTarget && this.turnToTarget) this._startCoroutine(this._turnToTarget.bind(this));
|
||||
}
|
||||
|
||||
// Quaternion.AngleAxis(rotation, ik.transform.up) * ik.transform.rotation;
|
||||
// let character = this.ik!.getComponent("CharacterOrientation") as CharacterOrientation;
|
||||
// character!.getUpdirection(_tempVec3);
|
||||
|
||||
Quat.fromAxisAngle(_tempQuat, Vec3.UP, math.toRadian(rotation));
|
||||
Quat.multiply(_tempQuat, _tempQuat, this.ik!.rootNode!.getWorldRotation());
|
||||
//this.ik!.rootNode!.setWorldRotation(_tempQuat);
|
||||
}
|
||||
}
|
||||
|
||||
// // Aligns the root forward to target direction after "Max Root Angle" has been exceeded.
|
||||
private _turnToTarget () {
|
||||
this.turningToTarget = true;
|
||||
|
||||
while (this.turnToTargetMlp > 0.0) {
|
||||
// this.turnToTargetMlp = Mathf.SmoothDamp(turnToTargetMlp, 0f, ref turnToTargetMlpV, turnToTargetTime);
|
||||
this.turnToTargetMlp -= game.deltaTime;
|
||||
if (this.turnToTargetMlp < 0.01) this.turnToTargetMlp = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(this._interval);
|
||||
this._interval = null;
|
||||
this.turnToTargetMlp = 1;
|
||||
}
|
||||
|
||||
private _startCoroutine (fb: Function) {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
}
|
||||
this._interval = setInterval(fb);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ik/aim-control.ts.meta
Normal file
9
assets/scripts/core/ik/aim-control.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "adfa726b-353e-48b8-a007-484c58412e9b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
40
assets/scripts/core/ik/aim-ik.ts
Normal file
40
assets/scripts/core/ik/aim-ik.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { _decorator, Component, Node, CCFloat } from 'cc';
|
||||
import IKBone from './ik-bone';
|
||||
import IKSolverAim from './ik-solver-aim';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('AimIK')
|
||||
export default class AimIK extends Component {
|
||||
public solver: IKSolverAim = new IKSolverAim(this.node);
|
||||
|
||||
@property({ type: Node, tooltip: 'this node that we want to aim at ikPosition.' })
|
||||
public rootNode : Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: 'this node that we want to aim at ikPosition.' })
|
||||
public aimNode : Node | null = null;
|
||||
|
||||
@property({ type: [Node], tooltip: 'bones node' })
|
||||
public boneNodes: Array<Node> = [];
|
||||
|
||||
@property({ type: [CCFloat], tooltip: 'bones weight' })
|
||||
public boneWeights : Array<number> = [];
|
||||
|
||||
public enableAim: boolean = true;
|
||||
|
||||
start () {
|
||||
this.boneNodes.forEach((node, index) => {
|
||||
this.solver.bones.push(new IKBone(node, this.boneWeights[index]));
|
||||
});
|
||||
|
||||
this.solver.rootNode = this.rootNode;
|
||||
this.solver.aimNode = this.aimNode;
|
||||
}
|
||||
|
||||
update () {
|
||||
this.enableAim && this.solver.update();
|
||||
}
|
||||
|
||||
lateUpdate () {
|
||||
this.enableAim && this.solver.lateUpdate();
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ik/aim-ik.ts.meta
Normal file
9
assets/scripts/core/ik/aim-ik.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "4022da37-69d5-4c16-8b52-6221fc9c3944",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
75
assets/scripts/core/ik/ik-bone.ts
Normal file
75
assets/scripts/core/ik/ik-bone.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Quat, Vec3, Node } from "cc";
|
||||
import MathUtil from "./math-util";
|
||||
import Point from "./ik-point";
|
||||
let _tempVec3 = new Vec3;
|
||||
let _tempVec3_2 = new Vec3;
|
||||
let _tempQuat = new Quat;
|
||||
let _tempQuat_2 = new Quat;
|
||||
|
||||
export default class Bone extends Point {
|
||||
public length: number = 0;
|
||||
public sqrMag: number = 0;
|
||||
public axis = new Vec3(1, 0, 0);
|
||||
|
||||
constructor (node: Node | null = null, weight:number | null = null) {
|
||||
super();
|
||||
if (node) {
|
||||
this.node = node;
|
||||
}
|
||||
if (weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
public get rotationLimit () {
|
||||
return null;
|
||||
}
|
||||
|
||||
public set rotationLimit (value) {
|
||||
return;
|
||||
}
|
||||
|
||||
public swing (swingTarget:Vec3, weight:number = 1.0) {
|
||||
if (weight <= 0.0) return;
|
||||
|
||||
Vec3.subtract(_tempVec3, swingTarget, this.node!.getWorldPosition());
|
||||
Vec3.transformQuat(_tempVec3_2, this.axis, this.node!.getWorldRotation());
|
||||
MathUtil.fromToRotation(_tempQuat, _tempVec3_2, _tempVec3);
|
||||
|
||||
if (weight >= 1.0) {
|
||||
Quat.multiply(_tempQuat, _tempQuat, this.node!.getWorldRotation());
|
||||
this.node?.setWorldRotation(_tempQuat);
|
||||
return;
|
||||
}
|
||||
MathUtil.quatLerp(_tempQuat, Quat.IDENTITY, _tempQuat, weight);
|
||||
Quat.multiply(_tempQuat, _tempQuat, this.node!.getWorldRotation());
|
||||
this.node!.setWorldRotation(_tempQuat);
|
||||
}
|
||||
|
||||
public static solverSwing (bones:Bone[], index:number, swingTarget:Vec3, weight:number = 1.0) {
|
||||
if (weight <= 0.0) return;
|
||||
|
||||
Vec3.subtract(_tempVec3, swingTarget, bones[index].solverPosition);
|
||||
Vec3.transformQuat(_tempVec3_2, bones[index].axis, bones[index].solverRotation);
|
||||
MathUtil.fromToRotation(_tempQuat, _tempVec3_2, _tempVec3);
|
||||
|
||||
if (weight >= 1.0) {
|
||||
for (let i = index; i < bones.length; i++) {
|
||||
Quat.multiply(bones[i].solverRotation, _tempQuat, bones[index].solverRotation,);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = index; i < bones.length; i++) {
|
||||
MathUtil.quatLerp(_tempQuat_2, Quat.IDENTITY, _tempQuat, weight);
|
||||
Quat.multiply(bones[i].solverRotation, _tempQuat_2, bones[index].solverRotation);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the bone to the solver position
|
||||
* */
|
||||
public setToSolverPosition () {
|
||||
this.node?.setWorldPosition(this.solverPosition);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ik/ik-bone.ts.meta
Normal file
9
assets/scripts/core/ik/ik-bone.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "49588808-d84e-4300-a2b2-56c65ef619fc",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
38
assets/scripts/core/ik/ik-point.ts
Normal file
38
assets/scripts/core/ik/ik-point.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { _decorator, Node, Vec3, Quat } from 'cc';
|
||||
export default class Point {
|
||||
public node: Node | null = null;
|
||||
public weight: number = 1.0;
|
||||
public solverPosition: Vec3 = new Vec3();
|
||||
public solverRotation:Quat = new Quat(); // Identity
|
||||
public defaultLocalPosition:Vec3 = new Vec3();
|
||||
public defaultLocalRotation:Quat = new Quat();
|
||||
|
||||
public storeDefaultLocalState () {
|
||||
this.node?.getPosition(this.defaultLocalPosition);
|
||||
this.node?.getRotation(this.defaultLocalRotation);
|
||||
}
|
||||
|
||||
public fixTransform () {
|
||||
if (!this.node?.position.equals(this.defaultLocalPosition)) {
|
||||
this.node?.setPosition(this.defaultLocalPosition);
|
||||
}
|
||||
|
||||
if (!this.node?.rotation.equals(this.defaultLocalRotation)) {
|
||||
this.node?.setRotation(this.defaultLocalRotation);
|
||||
}
|
||||
}
|
||||
|
||||
public updateSolverPosition () {
|
||||
this.node?.getWorldPosition(this.solverPosition);
|
||||
}
|
||||
|
||||
public updateSolverState () {
|
||||
this.node?.getWorldPosition(this.solverPosition);
|
||||
this.node?.getWorldRotation(this.solverRotation);
|
||||
}
|
||||
|
||||
public updateSolverLocalState () {
|
||||
this.node?.getPosition(this.solverPosition);
|
||||
this.node?.getRotation(this.solverRotation);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ik/ik-point.ts.meta
Normal file
9
assets/scripts/core/ik/ik-point.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "bf97861d-9b81-42fb-bc3a-4f018e7a6d39",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
222
assets/scripts/core/ik/ik-solver-aim.ts
Normal file
222
assets/scripts/core/ik/ik-solver-aim.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { _decorator, Node, Vec3, Quat, log } from 'cc';
|
||||
import { DEBUG } from 'cc/env';
|
||||
import MathUtil from './math-util';
|
||||
import IKBone from './ik-bone';
|
||||
import IKSolverHeuristic from './ik-solver-heuristic';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
let _tempVec3 = new Vec3;
|
||||
let _tempVec3_2 = new Vec3;
|
||||
let _tempVec3_3 = new Vec3;
|
||||
let _tempQuat = new Quat;
|
||||
let _tempQuat_2 = new Quat;
|
||||
let _time = 0;
|
||||
|
||||
@ccclass('IKSolverAim')
|
||||
export default class IKSolverAim extends IKSolverHeuristic {
|
||||
@property({ type: Node, tooltip: 'The transform that we want to aim at ikPosition.' })
|
||||
public aimNode: Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: 'The transform that we want to aim at ikPosition.' })
|
||||
public rootNode: Node | null = null;
|
||||
|
||||
@property({ tooltip: 'The local axis of the Transform that you want to be aimed at ikPosition.' })
|
||||
public axis:Vec3 = new Vec3(0, 0, 1);
|
||||
|
||||
@property({ tooltip: 'Keeps that axis of the Aim Transform directed at the polePosition.' })
|
||||
public poleAxis:Vec3 = new Vec3(0, 1, 0);
|
||||
|
||||
@property({ tooltip: 'The position in world space to keep the pole axis of the Aim Transform directed at' })
|
||||
public polePosition: Vec3 = new Vec3();
|
||||
|
||||
@property({ tooltip: 'The weight of the Pole.' })
|
||||
public poleWeight:number = 0;
|
||||
|
||||
@property({ type: Node, tooltip: 'If assigned, will automatically set polePosition to the position of this Transform.' })
|
||||
public poleTarget:Node | null = null;
|
||||
|
||||
@property({ tooltip: 'Clamping rotation of the solver. 0 is free rotation, 1 is completely clamped to transform axis.' })
|
||||
public clampWeight:number = 0;
|
||||
|
||||
@property({ tooltip: 'Number of sine smoothing iterations applied to clamping to make it smoother' })
|
||||
public clampSmoothing:number = 2;
|
||||
|
||||
protected minBones: number = 1;
|
||||
private _step : number = 0;
|
||||
private _clampedIKPosition:Vec3 = new Vec3;
|
||||
private _lastNode: Node|null = null;
|
||||
private _transformPoleAxis: Vec3 = new Vec3;
|
||||
private _transformAxis: Vec3 = new Vec3;
|
||||
|
||||
public getAngle ():number {
|
||||
Vec3.subtract(_tempVec3_3, this.ikPosition, this.aimNode!.getWorldPosition());
|
||||
return MathUtil.radiansToDegrees(Vec3.angle(this.transformAxis, _tempVec3_3));
|
||||
}
|
||||
|
||||
// Gets the Axis of the AimTransform is world space.
|
||||
public get transformAxis (): Vec3 {
|
||||
Vec3.transformQuat(this._transformAxis, this.axis, this.aimNode!.getWorldRotation());
|
||||
return Vec3.clone(this._transformAxis);
|
||||
}
|
||||
|
||||
// Gets the Pole Axis of the AimTransform is world space.
|
||||
public get transformPoleAxis () :Vec3 {
|
||||
return Vec3.transformQuat(this._transformPoleAxis, this.poleAxis, this.aimNode!.getWorldRotation());
|
||||
}
|
||||
|
||||
protected onInitiate () {
|
||||
if ((this.firstInitiation) && this.aimNode != null) {
|
||||
Vec3.add(this.ikPosition, this.aimNode.getWorldPosition(), this.transformAxis.multiplyScalar(3));
|
||||
Vec3.add(this.polePosition, this.aimNode.getWorldPosition(), this.transformPoleAxis.multiplyScalar(3));
|
||||
}
|
||||
|
||||
this._step = 1.0 / this.bones.length;
|
||||
this.axis.normalize();
|
||||
}
|
||||
|
||||
protected onUpdate () {
|
||||
if (this.axis.equals(Vec3.ZERO)) {
|
||||
if (!DEBUG) log("IKSolverAim axis is Vector3.zero.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.poleAxis.equals(Vec3.ZERO) && this.poleWeight > 0) {
|
||||
if (!DEBUG) log("IKSolverAim poleAxis is Vector3.zero.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.target != null) this.target.getWorldPosition(this.ikPosition);
|
||||
if (this.poleTarget != null) this.poleTarget.getWorldPosition(this.polePosition);
|
||||
|
||||
if (this.XY) this.ikPosition.z = this.bones[0].node!.getWorldPosition().z;
|
||||
|
||||
// Clamping weights
|
||||
if (this.ikPositionWeight <= 0) {
|
||||
_time = 0;
|
||||
return;
|
||||
}
|
||||
this.ikPositionWeight = MathUtil.clamp(this.ikPositionWeight, 0.0, 1.0);
|
||||
|
||||
// Rotation Limit on the Aim Transform
|
||||
if (this.aimNode != this._lastNode) {
|
||||
this._lastNode = this.aimNode;
|
||||
}
|
||||
|
||||
// In case transform becomes unassigned in runtime
|
||||
if (this.aimNode == null) {
|
||||
if (!DEBUG) log("Aim Transform unassigned in Aim IK solver. Please Assign a Transform (lineal descendant to the last bone in the spine) that you want to be aimed at ikPosition");
|
||||
return;
|
||||
}
|
||||
|
||||
this.clampWeight = MathUtil.clamp(this.clampWeight, 0.0, 1.0);
|
||||
this._clampedIKPosition = this._getClampedIKPosition();
|
||||
|
||||
Vec3.subtract(_tempVec3, this._clampedIKPosition, this.aimNode.getWorldPosition());
|
||||
Vec3.multiplyScalar(_tempVec3_2, this.transformAxis, _tempVec3.length());
|
||||
|
||||
MathUtil.sLerp(_tempVec3_2, _tempVec3_2, _tempVec3, this.ikPositionWeight);
|
||||
Vec3.add(this._clampedIKPosition, this.aimNode.getWorldPosition(), _tempVec3_2);
|
||||
|
||||
// Iterating the solver
|
||||
for (let i = 0; i < this.maxIterations; i++) {
|
||||
// Optimizations
|
||||
if (i >= 1 && this.tolerance > 0 && this.getAngle() < this.tolerance) break;
|
||||
this.lastLocalDirection = Vec3.clone(this.localDirection);
|
||||
this._solve();
|
||||
}
|
||||
|
||||
this.lastLocalDirection = Vec3.clone(this.localDirection);
|
||||
}
|
||||
|
||||
private _solve () {
|
||||
// Rotating bones to get closer to target.
|
||||
for (let i = 0; i < this.bones.length - 1; i++) this._rotateToTarget(this._clampedIKPosition, this.bones[i], this._step * (i + 1) * this.ikPositionWeight * this.bones[i].weight);
|
||||
this._rotateToTarget(this._clampedIKPosition, this.bones[this.bones.length - 1], this.ikPositionWeight * this.bones[this.bones.length - 1].weight);
|
||||
}
|
||||
|
||||
private _getClampedIKPosition ():Vec3 {
|
||||
if (this.clampWeight <= 0.0) return Vec3.clone(this.ikPosition);
|
||||
if (this.clampWeight >= 1.0) {
|
||||
Vec3.subtract(_tempVec3, this.ikPosition, this.aimNode!.getWorldPosition());
|
||||
Vec3.multiplyScalar(_tempVec3_2, this.transformAxis, _tempVec3.length());
|
||||
Vec3.add(_tempVec3_3, this.aimNode!.getWorldPosition(), _tempVec3_2);
|
||||
return Vec3.clone(_tempVec3_3);
|
||||
}
|
||||
|
||||
// Getting the dot product of IK direction and transformAxis
|
||||
Vec3.subtract(_tempVec3, this.ikPosition, this.aimNode!.getWorldPosition());
|
||||
let angle = MathUtil.radiansToDegrees(Vec3.angle(this.transformAxis, _tempVec3));
|
||||
let dot = 1.0 - (angle / 180.0);
|
||||
|
||||
// Clamping the target
|
||||
let targetClampMlp = this.clampWeight > 0 ? MathUtil.clamp(1.0 - ((this.clampWeight - dot) / (1.0 - dot)), 0.0, 1.0) : 1.0;
|
||||
|
||||
// Calculating the clamp multiplier
|
||||
let clampMlp = this.clampWeight > 0 ? MathUtil.clamp(dot / this.clampWeight, 0.0, 1.0) : 1.0;
|
||||
|
||||
for (let i = 0; i < this.clampSmoothing; i++) {
|
||||
let sinF = clampMlp * Math.PI * 0.5;
|
||||
clampMlp = Math.sin(sinF);
|
||||
}
|
||||
|
||||
// Slerping the IK direction (don't use Lerp here, it breaks it)
|
||||
Vec3.subtract(_tempVec3, this.ikPosition, this.aimNode!.getWorldPosition());
|
||||
Vec3.multiplyScalar(_tempVec3_2, this.transformAxis, 10);
|
||||
|
||||
// need slerp
|
||||
MathUtil.sLerp(_tempVec3_2, _tempVec3_2, _tempVec3, clampMlp * targetClampMlp);
|
||||
Vec3.add(_tempVec3_3, this.aimNode!.getWorldPosition(), _tempVec3_2);
|
||||
return Vec3.clone(_tempVec3_3);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotating bone to get transform aim closer to target
|
||||
* */
|
||||
private _rotateToTarget (targetPosition:Vec3, bone:IKBone, weight: number) {
|
||||
// Swing
|
||||
if (this.XY) {
|
||||
if (weight >= 0.0) {
|
||||
let dir = this.transformAxis;
|
||||
Vec3.subtract(_tempVec3, targetPosition, this.aimNode!.getWorldPosition());
|
||||
|
||||
let angleDir = MathUtil.radiansToDegrees(Math.atan2(dir.x, dir.y));
|
||||
let angleTarget = MathUtil.radiansToDegrees(Math.atan2(_tempVec3.x, _tempVec3.y));
|
||||
|
||||
let deltaAngle = MathUtil.deltaAngle(angleDir, angleTarget);
|
||||
|
||||
MathUtil.axisAngle(_tempQuat, new Vec3(0, 0, -1), deltaAngle);
|
||||
Quat.multiply(_tempQuat, _tempQuat, bone.node!.getWorldRotation());
|
||||
bone.node?.setRotation(_tempQuat);
|
||||
}
|
||||
} else {
|
||||
if (weight >= 0.0) {
|
||||
Vec3.subtract(_tempVec3, targetPosition, this.aimNode!.getWorldPosition());
|
||||
// MathUtil.fromToRotation(_tempQuat, this.transformAxis, _tempVec3);
|
||||
Quat.rotationTo(_tempQuat, this.transformAxis.normalize(), _tempVec3.normalize());
|
||||
if (weight >= 1.0) {
|
||||
Quat.multiply(_tempQuat, _tempQuat, bone.node!.getWorldRotation());
|
||||
bone.node!.setWorldRotation(_tempQuat);
|
||||
} else {
|
||||
MathUtil.quatLerp(_tempQuat, Quat.IDENTITY, _tempQuat, weight);
|
||||
Quat.multiply(_tempQuat, _tempQuat, bone.node!.getWorldRotation());
|
||||
Quat.normalize(_tempQuat, _tempQuat);
|
||||
bone.node!.setWorldRotation(_tempQuat);
|
||||
}
|
||||
}
|
||||
|
||||
// Pole
|
||||
if (this.poleWeight > 0.0) {
|
||||
// wait to do
|
||||
// Vec3.subtract(_tempVec3, this.polePosition , this.aimNode!.getWorldPosition())
|
||||
// // Ortho-normalize to transform axis to make this a twisting only operation
|
||||
// Vec3.copy(_tempVec3_2, _tempVec3);
|
||||
// let normal = this.transformAxis;
|
||||
// Vector3.OrthoNormalize(ref normal, ref poleDirOrtho);
|
||||
// Quaternion toPole = Quaternion.FromToRotation(this.transformPoleAxis, poleDirOrtho);
|
||||
// bone.node.rotation = Quaternion.Lerp(Quaternion.identity, toPole, weight * this.poleWeight) * bone.node.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
9
assets/scripts/core/ik/ik-solver-aim.ts.meta
Normal file
9
assets/scripts/core/ik/ik-solver-aim.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "b50c3c9c-a904-4a50-a173-5591d5d0dce7",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
162
assets/scripts/core/ik/ik-solver-heuristic.ts
Normal file
162
assets/scripts/core/ik/ik-solver-heuristic.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { _decorator, Node, Vec3, Quat, CCFloat } from 'cc';
|
||||
import MathUtil from './math-util';
|
||||
import IKBone from './ik-bone';
|
||||
import IKPoint from "./ik-point";
|
||||
import IKSolver from './ik-solver';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
let _tempVec3 = new Vec3;
|
||||
let _tempVec3_2 = new Vec3;
|
||||
let _tempQuat = new Quat;
|
||||
let _tempQuat_2 = new Quat;
|
||||
|
||||
@ccclass('IKSolverHeuristic')
|
||||
export default class IKSolverHeuristic extends IKSolver {
|
||||
@property({ type: Node, tooltip: 'Reference to the AimIK component.' })
|
||||
public target: Node | null = null;
|
||||
|
||||
@property({ tooltip: 'Minimum distance from last reached position. Will stop solving if difference from previous reached position is less than tolerance. If tolerance is zero, will iterate until maxIterations.' })
|
||||
public tolerance: number = 0;
|
||||
|
||||
@property({ tooltip: 'Max iterations per frame.' })
|
||||
public maxIterations:number = 4;
|
||||
|
||||
@property({ tooltip: 'Solve in 2D?' })
|
||||
public XY :boolean = false;
|
||||
|
||||
|
||||
// @property({type:[Node], tooltip: 'boneNodes'})
|
||||
// public boneNodes :Node[] = [];
|
||||
|
||||
// @property({type:[CCFloat],tooltip: 'boneWeights'})
|
||||
// public boneWeights: number [] = [];
|
||||
|
||||
|
||||
// @property({type:[IKBone], tooltip: 'The hierarchy of bones.'})
|
||||
public bones: IKBone [] = [];
|
||||
|
||||
protected lastLocalDirection: Vec3 | null = null;
|
||||
protected chainLength: number = 0;
|
||||
private _localDirection: Vec3 = new Vec3;
|
||||
|
||||
// initBones (){
|
||||
// this.boneNodes.forEach((node, index)=>{
|
||||
// this.bones.push(new IKBone(node, this.boneWeights[index]));
|
||||
// })
|
||||
// }
|
||||
|
||||
public setChain (hierarchy:Node[], root:Node)
|
||||
{
|
||||
if (this.bones == null || this.bones.length != hierarchy.length) this.bones = new Array<IKBone>(hierarchy.length);
|
||||
for (let i = 0; i < hierarchy.length; i++)
|
||||
{
|
||||
if (this.bones[i] == null) this.bones[i] = new IKBone();
|
||||
this.bones[i].node = hierarchy[i];
|
||||
}
|
||||
|
||||
this.initiate(root);
|
||||
return this.initiated;
|
||||
}
|
||||
|
||||
public addBone (bone:Node)
|
||||
{
|
||||
let newBones:Node[] = new Array<Node>(this.bones.length + 1);
|
||||
|
||||
for (let i = 0; i < this.bones.length; i++)
|
||||
{
|
||||
newBones[i] = this.bones[i].node!;
|
||||
}
|
||||
|
||||
newBones[newBones.length - 1] = bone;
|
||||
|
||||
this.setChain(newBones, this.root!);
|
||||
}
|
||||
|
||||
public storeDefaultLocalState ()
|
||||
{
|
||||
for (let i = 0; i < this.bones.length; i++) this.bones[i].storeDefaultLocalState();
|
||||
}
|
||||
|
||||
public fixTransforms ()
|
||||
{
|
||||
if (!this.initiated) return;
|
||||
if (this.ikPositionWeight <= 0.0) return;
|
||||
|
||||
for (let i = 0; i < this.bones.length; i++) this.bones[i].fixTransform();
|
||||
}
|
||||
|
||||
public getPoints ()
|
||||
{
|
||||
return this.bones;
|
||||
}
|
||||
|
||||
public getPoint (node:Node) : IKPoint | null
|
||||
{
|
||||
for (let i = 0; i < this.bones.length; i++) if (this.bones[i].node == node) return this.bones[i] as IKPoint;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected initiateBones ()
|
||||
{
|
||||
this.chainLength = 0;
|
||||
|
||||
for (let i = 0; i < this.bones.length; i++)
|
||||
{
|
||||
// Find out which local axis is directed at child/target position
|
||||
if (i < this.bones.length - 1)
|
||||
{
|
||||
Vec3.subtract(_tempVec3, this.bones[i].node!.getWorldPosition(), this.bones[i + 1].node!.getWorldPosition());
|
||||
this.bones[i].length = _tempVec3.length();
|
||||
this.chainLength += this.bones[i].length;
|
||||
|
||||
this.bones[i + 1].node!.getWorldPosition(_tempVec3_2);
|
||||
Vec3.subtract(_tempVec3_2, _tempVec3_2, this.bones[i].node!.getWorldPosition());
|
||||
Quat.invert(_tempQuat, this.bones[i].node!.getWorldRotation());
|
||||
Vec3.transformQuat(this.bones[i].axis, _tempVec3_2, _tempQuat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec3.subtract(_tempVec3, this.bones[this.bones.length - 1].node!.getWorldPosition(), this.bones[0].node!.getWorldPosition());
|
||||
Quat.invert(_tempQuat, this.bones[i].node!.getWorldRotation());
|
||||
Vec3.transformQuat(this.bones[i].axis, _tempVec3_2, _tempQuat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected get localDirection () :Vec3 {
|
||||
MathUtil.directionToNodeSpace(this._localDirection, this.bones[this.bones.length - 1].node!.getWorldPosition().subtract(this.bones[0].node!.getWorldPosition()), this.bones[0].node!);
|
||||
return this._localDirection.clone();
|
||||
}
|
||||
|
||||
protected get positionOffset () : number {
|
||||
return Vec3.lengthSqr(this.localDirection.subtract(this.lastLocalDirection!));
|
||||
}
|
||||
|
||||
protected getSingularityOffset ()
|
||||
{
|
||||
if (!this._singularityDetected()) return Vec3.ZERO;
|
||||
|
||||
Vec3.subtract(_tempVec3, this.ikPosition, this.bones[0].node!.getWorldPosition());
|
||||
_tempVec3.normalize();
|
||||
|
||||
let secondaryDirection:Vec3 = new Vec3(_tempVec3.y, _tempVec3.z, _tempVec3.x);
|
||||
Vec3.cross(_tempVec3, _tempVec3, secondaryDirection);
|
||||
|
||||
return _tempVec3.multiplyScalar(this.bones[this.bones.length - 2].length * 0.5).clone();
|
||||
}
|
||||
|
||||
private _singularityDetected ()
|
||||
{
|
||||
if (!this.initiated) return false;
|
||||
|
||||
Vec3.subtract(_tempVec3, this.bones[this.bones.length - 1].node!.getWorldPosition(), this.bones[0].node!.getWorldPosition());
|
||||
Vec3.subtract(_tempVec3_2, this.ikPosition, this.bones[0].node!.getWorldPosition());
|
||||
|
||||
let toLastBoneDistance = _tempVec3.length();
|
||||
let toIKPositionDistance = _tempVec3_2.length();
|
||||
|
||||
if (toLastBoneDistance < toIKPositionDistance) return false;
|
||||
if (toLastBoneDistance < this.chainLength - (this.bones[this.bones.length - 2].length * 0.1)) return false;
|
||||
}
|
||||
|
||||
}
|
||||
9
assets/scripts/core/ik/ik-solver-heuristic.ts.meta
Normal file
9
assets/scripts/core/ik/ik-solver-heuristic.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "7ac3d308-b53d-4228-a8ab-5a16d8081eec",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
55
assets/scripts/core/ik/ik-solver.ts
Normal file
55
assets/scripts/core/ik/ik-solver.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { _decorator, Component, Node, Vec3, RigidBody, physics, v3, Quat, director, quat, CCBoolean } from 'cc';
|
||||
import Bone from './ik-bone';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('IKSolver')
|
||||
export default class IKSolver {
|
||||
// @property({type:CCBoolean,tooltip: 'If true, will fix all the Transforms used by the solver to their initial state in each Update. This prevents potential problems with unanimated bones and animator culling with a small cost of performance. Not recommended for CCD and FABRIK solvers.'})
|
||||
public isFixTransforms: boolean = true;
|
||||
|
||||
public initiated: boolean = false;
|
||||
protected root: Node | null = null;
|
||||
protected firstInitiation: boolean = true;
|
||||
public ikPosition: Vec3 = new Vec3();
|
||||
public ikPositionWeight: number = 1.0;
|
||||
|
||||
|
||||
protected onInitiate () {}
|
||||
protected storeDefaultLocalState () {}
|
||||
protected onUpdate () {}
|
||||
protected fixTransforms () {}
|
||||
|
||||
constructor (root:Node) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public initiate (root:Node | null) {
|
||||
this.root = root;
|
||||
this.initiated = false;
|
||||
|
||||
this.onInitiate();
|
||||
this.storeDefaultLocalState();
|
||||
this.initiated = true;
|
||||
this.firstInitiation = false;
|
||||
}
|
||||
|
||||
public lateUpdate () {
|
||||
if (this.firstInitiation) this.initiate(this.root); // when the IK component has been disabled in Awake, this will initiate it.
|
||||
if (!this.initiated) return;
|
||||
|
||||
this.onUpdate();
|
||||
}
|
||||
|
||||
public static containsDuplicateBone (bones:Bone[]) {
|
||||
for (let i = 0; i < bones.length; i++) {
|
||||
for (let i2 = 0; i2 < bones.length; i2++) {
|
||||
if (i != i2 && bones[i].node == bones[i2].node) return bones[i].node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public update () {
|
||||
this.isFixTransforms && this.fixTransforms();
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ik/ik-solver.ts.meta
Normal file
9
assets/scripts/core/ik/ik-solver.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "79994cf3-de76-4262-b041-81cc28d5a943",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
383
assets/scripts/core/ik/math-util.ts
Normal file
383
assets/scripts/core/ik/math-util.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
import { _decorator, Vec3, Node, Mat4, Quat, geometry, PhysicsSystem, physics, Vec2, math, director, quat, game } from 'cc';
|
||||
//import { ColliderGroup, ControlType, CameraRotateType } from '../scene/define';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
let _tempQuat: Quat = new Quat();
|
||||
let _tempVec3: Vec3 = new Vec3;
|
||||
let _tempVec3_2: Vec3 = new Vec3;
|
||||
|
||||
let _tempMat: Mat4 = new Mat4;
|
||||
let _tempMat_2: Mat4 = new Mat4;
|
||||
|
||||
let _forward: Vec3 = new Vec3(0, 0, 1);
|
||||
|
||||
let _ray: geometry.Ray = new geometry.Ray;
|
||||
|
||||
@ccclass('MathUtil')
|
||||
export default class MathUtil {
|
||||
public static degreesToRadians (degValue: number): number {
|
||||
return degValue * (Math.PI / 180.0);
|
||||
}
|
||||
|
||||
public static radiansToDegrees (radValue: number): number {
|
||||
return radValue * (180.0 / Math.PI);
|
||||
}
|
||||
|
||||
public static clamp01 (value: number): number {
|
||||
if (value < 0) return 0;
|
||||
if (value > 1) return 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static clampDegrees (value: number, min: number = -180, max: number = 180): number {
|
||||
while (value < min) {
|
||||
value += 360;
|
||||
}
|
||||
while (value > max) {
|
||||
value -= 360;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static transformDegreesToNear (value: number, target: number) {
|
||||
if (Math.abs(value - target) < 180) return value;
|
||||
if (value < target) {
|
||||
while (value < target) {
|
||||
value += 360;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
while (value > target) {
|
||||
value -= 360;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static clamp (value: number, min: number, max: number): number {
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static inverseLerp (a: number, b: number, v: number) {
|
||||
let ba = b - a;
|
||||
if (Math.abs(ba) < 0.000001) return 0;
|
||||
v = (v - a) / ba;
|
||||
return MathUtil.clamp01(v);
|
||||
}
|
||||
|
||||
public static remap (x: number, a: number, b: number, c: number, d: number): number {
|
||||
let remappedValue: number = c + (x - a) / (b - a) * (d - c);
|
||||
return remappedValue;
|
||||
}
|
||||
|
||||
public static convertToNodeSpace (out: Vec3, worldPosition: Vec3, node: Node) {
|
||||
node.getWorldMatrix(_tempMat);
|
||||
Mat4.invert(_tempMat_2, _tempMat);
|
||||
return Vec3.transformMat4(out, worldPosition, _tempMat_2);
|
||||
}
|
||||
|
||||
public static convertToWorldSpace (out: Vec3, localPosition: Vec3, node: Node) {
|
||||
node.getWorldMatrix(_tempMat);
|
||||
return Vec3.transformMat4(out, localPosition, _tempMat);
|
||||
}
|
||||
|
||||
public static randomInt (min: number, max: number) {
|
||||
if (min > max) return -1;
|
||||
return min + Math.round((max - min) * Math.random());
|
||||
}
|
||||
|
||||
public static directionToNodeSpace (out: Vec3, worldDirection: Vec3, node: Node) {
|
||||
node.getWorldPosition(_tempVec3);
|
||||
_tempVec3.add(worldDirection);
|
||||
MathUtil.convertToNodeSpace(out, _tempVec3, node);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static getWorldLine (beginNode: Node, endNode: Node, worldRotation: Quat, worldPosition: Vec3) {
|
||||
beginNode.getWorldPosition(_tempVec3);
|
||||
endNode.getWorldPosition(_tempVec3_2);
|
||||
|
||||
Vec3.lerp(worldPosition, _tempVec3, _tempVec3_2, 0.5);
|
||||
|
||||
_tempVec3_2.subtract(_tempVec3);
|
||||
let length = _tempVec3_2.length();
|
||||
_tempVec3_2.normalize();
|
||||
|
||||
Quat.rotationTo(worldRotation, _forward, _tempVec3_2);
|
||||
return length;
|
||||
}
|
||||
|
||||
public static getWorldLineByPos (beginPos: Vec3, endPos: Vec3, worldRotation: Quat, worldPosition: Vec3) {
|
||||
Vec3.copy(_tempVec3, beginPos);
|
||||
Vec3.copy(_tempVec3_2, endPos);
|
||||
|
||||
Vec3.lerp(worldPosition, _tempVec3, _tempVec3_2, 0.5);
|
||||
|
||||
_tempVec3_2.subtract(_tempVec3);
|
||||
let length = _tempVec3_2.length();
|
||||
_tempVec3_2.normalize();
|
||||
|
||||
Quat.rotationTo(worldRotation, _forward, _tempVec3_2);
|
||||
return length;
|
||||
}
|
||||
|
||||
/*
|
||||
public static getFieldViewPoint (points: Node[], testPoint: Node, resultPoints: Node[], pointMask = ColliderGroup.PathPointAim, resultPointsMap: Map<Node, boolean> | null = null, yEnoughDistance = 99999) {
|
||||
testPoint.getWorldPosition(_tempVec3);
|
||||
resultPoints.length = 0;
|
||||
if (resultPointsMap) {
|
||||
resultPointsMap.clear();
|
||||
}
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
let point = points[i];
|
||||
if (testPoint == point) continue;
|
||||
|
||||
point.getWorldPosition(_tempVec3_2);
|
||||
|
||||
let yDistance = Math.abs(_tempVec3.y - _tempVec3_2.y);
|
||||
if (yDistance > yEnoughDistance) continue;
|
||||
|
||||
Vec3.subtract(_tempVec3_2, _tempVec3_2, _tempVec3);
|
||||
_tempVec3_2.normalize();
|
||||
geometry.Ray.set(_ray,
|
||||
_tempVec3.x, _tempVec3.y, _tempVec3.z,
|
||||
_tempVec3_2.x, _tempVec3_2.y, _tempVec3_2.z);
|
||||
|
||||
let hasHit = PhysicsSystem.instance.raycastClosest(_ray, pointMask);
|
||||
if (!hasHit) continue;
|
||||
let hitResult = PhysicsSystem.instance.raycastClosestResult;
|
||||
if (hitResult.collider.node != point) continue;
|
||||
|
||||
(point as any).__distance = hitResult.distance;
|
||||
resultPoints.push(point);
|
||||
if (resultPointsMap) {
|
||||
resultPointsMap.set(point, true);
|
||||
}
|
||||
}
|
||||
|
||||
resultPoints.sort((a, b) => (a as any).__distance - (b as any).__distance);
|
||||
return resultPoints;
|
||||
}
|
||||
*/
|
||||
|
||||
public static distance (a: Node, b: Node) {
|
||||
a.getWorldPosition(_tempVec3);
|
||||
b.getWorldPosition(_tempVec3_2);
|
||||
return Vec3.distance(_tempVec3, _tempVec3_2);
|
||||
}
|
||||
|
||||
public static hDistance (a: Vec3, b: Vec3) {
|
||||
Vec3.copy(_tempVec3, a);
|
||||
Vec3.copy(_tempVec3_2, b);
|
||||
_tempVec3.y = 0;
|
||||
_tempVec3_2.y = 0;
|
||||
return Vec3.distance(_tempVec3, _tempVec3_2);
|
||||
}
|
||||
|
||||
public static getMoveDirectionByCameraDirection (out:Vec3, rotateValue: Vec2, rotateVector: Vec3, node: Node) {
|
||||
let x = rotateValue.x;
|
||||
let y = rotateValue.y;
|
||||
let deg = Math.atan2(-y, x) - Math.PI * 0.5;
|
||||
|
||||
Vec3.rotateY(out, rotateVector, Vec3.ZERO, deg);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static getLocalDegree (rotateValue: Vec2, rotateVector: Vec3, node: Node) {
|
||||
// because input is base on engine z and x axis, so it's like
|
||||
/*
|
||||
|
|
||||
____|_____\ x
|
||||
| /
|
||||
|
|
||||
\ /
|
||||
z
|
||||
*/
|
||||
// now we need to handle direction with the camera observe direction, so we need to reversal the z axis, the z is primary movement's y axis
|
||||
// the x and y is zero when beginning, that's mean it point to x axis, but camera point to -z direction, so need to minus 90
|
||||
let x = rotateValue.x;
|
||||
let y = rotateValue.y;
|
||||
let deg = Math.atan2(-y, x) - Math.PI * 0.5;
|
||||
|
||||
Vec3.rotateY(_tempVec3, rotateVector, Vec3.ZERO, deg);
|
||||
node.getWorldPosition(_tempVec3_2);
|
||||
_tempVec3_2.add(_tempVec3);
|
||||
MathUtil.convertToNodeSpace(_tempVec3, _tempVec3_2, node);
|
||||
_tempVec3.y = 0;
|
||||
_tempVec3.normalize();
|
||||
return MathUtil.radiansToDegrees(Math.atan2(_tempVec3.x, _tempVec3.z));
|
||||
}
|
||||
|
||||
public static smoothDamp (current:number, target:number, currentVelocity: {value:number}, smoothTime:number, maxSpeed:number = 100) {
|
||||
smoothTime = Math.max(0.0001, smoothTime);
|
||||
let num1 = 2.0 / smoothTime;
|
||||
let num2 = num1 * game.deltaTime;
|
||||
let num3 = (1.0 / (1.0 + num2 + 0.479999989271164 * num2 * num2 + 0.234999999403954 * num2 * num2 * num2));
|
||||
let num4 = current - target;
|
||||
let num5 = target;
|
||||
let max = maxSpeed * smoothTime;
|
||||
let num6 = MathUtil.clamp(num4, -max, max);
|
||||
target = current - num6;
|
||||
let num7 = (currentVelocity.value + num1 * num6) * game.deltaTime;
|
||||
currentVelocity.value = (currentVelocity.value - num1 * num7) * num3;
|
||||
let num8 = target + (num6 + num7) * num3;
|
||||
if (num5 - current > 0.0 == num8 > num5)
|
||||
{
|
||||
num8 = num5;
|
||||
currentVelocity.value = (num8 - num5) / game.deltaTime;
|
||||
}
|
||||
return num8;
|
||||
}
|
||||
|
||||
// it equal Quat.rotationTo(_tempQuat, fromDir.normalize(), toDir.normalize());
|
||||
public static fromToRotation (out:Quat, fromDir:Vec3, toDir:Vec3) {
|
||||
if (fromDir.equals(Vec3.ZERO) || toDir.equals(Vec3.ZERO)) return Quat.IDENTITY;
|
||||
|
||||
// fromDir normalize by max
|
||||
let max = Math.abs(fromDir.x);
|
||||
max = max > Math.abs(fromDir.y) ? max : Math.abs(fromDir.y);
|
||||
max = (max > Math.abs(fromDir.z)) ? max : Math.abs(fromDir.z);
|
||||
fromDir = fromDir.multiplyScalar(1 / max); // fromDir = fromDir / max;
|
||||
|
||||
// toDir normalize by max
|
||||
max = Math.abs(toDir.x);
|
||||
max = (max > Math.abs(toDir.y)) ? max : Math.abs(toDir.y);
|
||||
max = (max > Math.abs(toDir.z)) ? max : Math.abs(toDir.z);
|
||||
toDir = toDir.multiplyScalar(1 / max); // toDir = toDir / max;
|
||||
|
||||
// set miniThreshold
|
||||
let miniThreshold = 0.001;
|
||||
fromDir.x = Math.abs(fromDir.x) <= miniThreshold ? 0 : fromDir.x;
|
||||
fromDir.y = Math.abs(fromDir.y) <= miniThreshold ? 0 : fromDir.y;
|
||||
fromDir.z = Math.abs(fromDir.z) <= miniThreshold ? 0 : fromDir.z;
|
||||
toDir.x = Math.abs(toDir.x) <= miniThreshold ? 0 : toDir.x;
|
||||
toDir.y = Math.abs(toDir.y) <= miniThreshold ? 0 : toDir.y;
|
||||
toDir.z = Math.abs(toDir.z) <= miniThreshold ? 0 : toDir.z;
|
||||
|
||||
Vec3.normalize(_tempVec3, toDir);
|
||||
Vec3.normalize(_tempVec3_2, fromDir);
|
||||
Vec3.add(_tempVec3, _tempVec3, _tempVec3_2);
|
||||
|
||||
let mid:Vec3 = new Vec3();
|
||||
Vec3.normalize(mid, _tempVec3);
|
||||
if (mid.equals(Vec3.ZERO))
|
||||
{
|
||||
// X
|
||||
if (fromDir.x != 0 && fromDir.y == 0 && fromDir.z == 0) { return new Quat(0, 1, 0, 0) }
|
||||
// Y
|
||||
else if (fromDir.x == 0 && fromDir.y != 0 && fromDir.z == 0) { return new Quat(1, 0, 0, 0) }
|
||||
// Z
|
||||
else if (fromDir.x == 0 && fromDir.y == 0 && fromDir.z != 0) { return new Quat(1, 0, 0, 0) }
|
||||
// X
|
||||
else if (fromDir.x == 0 && fromDir.y != 0 && fromDir.z != 0) { return new Quat(1, 0, 0, 0) }
|
||||
// Y
|
||||
else if (fromDir.x != 0 && fromDir.y == 0 && fromDir.z != 0)
|
||||
{
|
||||
Vec3.normalize(_tempVec3, toDir);
|
||||
Vec3.normalize(_tempVec3_2, fromDir);
|
||||
let X = _tempVec3.z;
|
||||
let Z = _tempVec3_2.x;
|
||||
|
||||
if (X + Z < 0 || (X + Z == 0 && X < 0)) { return new Quat(-X, 0, -Z, 0) }
|
||||
else { return new Quat(X, 0, Z, 0) }
|
||||
}
|
||||
// Z
|
||||
else if (fromDir.x != 0 && fromDir.y != 0 && fromDir.z == 0)
|
||||
{
|
||||
Vec3.normalize(_tempVec3, toDir);
|
||||
Vec3.normalize(_tempVec3_2, fromDir);
|
||||
let X = _tempVec3.y;
|
||||
let Y = _tempVec3_2.x;
|
||||
|
||||
if (X + Y < 0 || (X + Y == 0 && X < 0)) { return new Quat(-X, -Y, 0, 0) }
|
||||
else { return new Quat(X, Y, 0, 0) }
|
||||
}
|
||||
else
|
||||
{
|
||||
mid.y = fromDir.z;
|
||||
mid.z = toDir.y;
|
||||
mid.normalize();
|
||||
}
|
||||
}
|
||||
Vec3.normalize(_tempVec3, toDir);
|
||||
Vec3.normalize(_tempVec3_2, mid);
|
||||
Quat.multiply(out, new Quat(-_tempVec3.x, -_tempVec3.y, -_tempVec3.z, 0), new Quat(_tempVec3_2.x, _tempVec3_2.y, _tempVec3_2.z, 0));
|
||||
return out;
|
||||
}
|
||||
|
||||
public static deltaAngle (src:number, tar:number) {
|
||||
src = src % 360;
|
||||
tar = tar % 360;
|
||||
return tar - src > 180 ? (src - tar) : tar - src;
|
||||
}
|
||||
|
||||
// axisAngle = Quat.fromAxisAngle
|
||||
public static axisAngle (out:Quat, a1:Vec3, angle:number) {
|
||||
let s = Math.sin(angle / 2);
|
||||
out = new Quat(a1.x * s, a1.y * s, a1.z * s, Math.cos(angle / 2));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Quat.lerp normalize out
|
||||
public static quatLerp (out: Quat, a: Quat, b: Quat, t: number) {
|
||||
if (Quat.dot(a, b) < 0.0) {
|
||||
_tempQuat.set(-b.x, -b.y, -b.z, -b.w);
|
||||
}
|
||||
else {
|
||||
_tempQuat.set(b);
|
||||
}
|
||||
Quat.lerp(out, a, _tempQuat, MathUtil.clamp01(t));
|
||||
Quat.normalize(out, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Vector sLerp
|
||||
// v(t) = k(t)*(v1 + t*(v2-v1))
|
||||
// k(t) = |v1|/|v(t)|=|v1|/|v1+t*(v2-v1)|
|
||||
public static sLerp (out:Vec3, a: Vec3, b:Vec3, t: number) {
|
||||
Vec3.subtract(_tempVec3, b, a);
|
||||
Vec3.multiplyScalar(_tempVec3, _tempVec3, t);
|
||||
Vec3.add(_tempVec3_2, a, _tempVec3);
|
||||
|
||||
let k = a.length() / _tempVec3_2.length();
|
||||
Vec3.multiplyScalar(out, _tempVec3_2, k);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
public static orthoNormalize (outA:Vec3, outB: Vec3, direction:Vec3, center:Vec3) {
|
||||
Vec3.normalize(_tempVec3, direction);
|
||||
Vec3.rotateY(outA, _tempVec3, center, 90);
|
||||
Vec3.cross(outB, outA, _tempVec3);
|
||||
}
|
||||
|
||||
public static clampVec3 (out:Vec3, minVec:Vec3 | null = null, maxVec:Vec3|null = null) {
|
||||
if (minVec) {
|
||||
if (out.x < minVec.x) {
|
||||
out.x = minVec.x;
|
||||
}
|
||||
if (out.y < minVec.y) {
|
||||
out.y = minVec.z;
|
||||
}
|
||||
if (out.z < minVec.y) {
|
||||
out.z = minVec.z;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxVec) {
|
||||
if (out.x > maxVec.x) {
|
||||
out.x = maxVec.x;
|
||||
}
|
||||
if (out.y > maxVec.y) {
|
||||
out.y = maxVec.z;
|
||||
}
|
||||
if (out.z > maxVec.y) {
|
||||
out.z = maxVec.z;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ik/math-util.ts.meta
Normal file
9
assets/scripts/core/ik/math-util.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "deef4dbc-7fd2-4e3c-b7a3-5cefae347cab",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
238
assets/scripts/core/ik/weapon-aim.ts
Normal file
238
assets/scripts/core/ik/weapon-aim.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
import { _decorator, Node, Component, ccenum, Quat, error, Vec3, view } from 'cc';
|
||||
import MathUtil from './math-util';
|
||||
//import { Weapon } from './weapon';
|
||||
//import { CharacterTypes } from '../core/character';
|
||||
//import MathUtil from '../util/math-util';
|
||||
//import CinemachineCameraManager from '../cinemachine/cinemachine-camera-manager';
|
||||
//import { ColliderGroup, ControlType } from '../scene/define';
|
||||
//import { GameManager } from '../manager/game-manager';
|
||||
const { ccclass, property, type } = _decorator;
|
||||
|
||||
// / the list of possible control modes
|
||||
export enum AimControls {
|
||||
Auto,
|
||||
Center,
|
||||
Mouse
|
||||
}
|
||||
ccenum(AimControls);
|
||||
|
||||
export enum AimRotateType {
|
||||
ByNone,
|
||||
ByCharacter,
|
||||
ByWeapon
|
||||
}
|
||||
ccenum(AimRotateType);
|
||||
|
||||
let _tempQuat: Quat = new Quat;
|
||||
let _tempVec3: Vec3 = new Vec3;
|
||||
@ccclass('WeaponAim')
|
||||
export class WeaponAim extends Component {
|
||||
@type(AimRotateType)
|
||||
yawRotateType: AimRotateType = AimRotateType.ByCharacter;
|
||||
|
||||
@type(AimRotateType)
|
||||
pitchRotateType: AimRotateType = AimRotateType.ByCharacter;
|
||||
|
||||
//public mask: number = ColliderGroup.All;
|
||||
public aimControl: AimControls = AimControls.Mouse;
|
||||
|
||||
@property
|
||||
isAutoAim = false;
|
||||
|
||||
@property
|
||||
public pitchWhenYawInRange = 10;
|
||||
|
||||
@property
|
||||
public maxPitch = 80;
|
||||
|
||||
@property
|
||||
public minPitch = -80;
|
||||
|
||||
@property
|
||||
public aimReadyValue = 2;
|
||||
|
||||
@property
|
||||
aimRange: number = 0.2;
|
||||
|
||||
// / the weapon's current direction
|
||||
public get currentAim () { return this._currentAim }
|
||||
// / the current angle the weapon is aiming at
|
||||
public currentYaw = 0;
|
||||
public currentPitch = 0;
|
||||
|
||||
//protected _weapon: Weapon | null = null;
|
||||
protected _currentAim: Vec3 = new Vec3;
|
||||
protected _lookRotation: Quat = new Quat;
|
||||
|
||||
public aimWasReady = false;
|
||||
|
||||
protected _currentTarget: Node | null = null;
|
||||
public set currentTarget (value: Node | null) {
|
||||
if (value == this._currentTarget) {
|
||||
return;
|
||||
}
|
||||
this.aimWasReady = false;
|
||||
this._currentTarget = value;
|
||||
}
|
||||
|
||||
start () {
|
||||
this.initialization();
|
||||
}
|
||||
|
||||
initialization () {
|
||||
//this._weapon = this.getComponent(Weapon);
|
||||
}
|
||||
|
||||
// Computes the current aim direction
|
||||
updateCurrentAim () {
|
||||
|
||||
/*
|
||||
if (!this._weapon || !this._weapon.owner || this._weapon.reloading) return;
|
||||
let owner = this._weapon.owner;
|
||||
if (owner.characterType == CharacterTypes.Player && !owner.linkedInputManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
let controlType = owner.controlType;
|
||||
this.mask = controlType == ControlType.TopDown ? ColliderGroup.TopDownAim : ColliderGroup.ThirdPersonAim;
|
||||
*/
|
||||
|
||||
switch (this.aimControl) {
|
||||
case AimControls.Center:
|
||||
this.updateCenterAim();
|
||||
break;
|
||||
case AimControls.Mouse:
|
||||
this.updateMouseAim();
|
||||
break;
|
||||
case AimControls.Auto:
|
||||
this.updateAutoAim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateAutoAim () {
|
||||
if (this._currentTarget) {
|
||||
this._currentTarget!.getWorldPosition(this._currentAim);
|
||||
|
||||
// debug auto target
|
||||
// gameMgr.drawNode(this._currentTarget!);
|
||||
this.calculateCurrentAim();
|
||||
} else {
|
||||
this.currentPitch = 0;
|
||||
this.aimWasReady = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateCenterAim () {
|
||||
let viewport = view.getViewportRect();
|
||||
let x = viewport.width / 2;
|
||||
let y = viewport.height / 2;
|
||||
|
||||
// get the position behind the weapon
|
||||
/*
|
||||
let success = CinemachineCameraManager.instance.getScreenPointToWorldPositionWithNodeDistance(this._currentAim, x, y, this.mask, this._weapon?.getRotatingModel() || this.node);
|
||||
if (!success) {
|
||||
this.currentPitch = 0;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
this.calculateCurrentAim();
|
||||
}
|
||||
|
||||
updateMouseAim () {
|
||||
|
||||
/*
|
||||
if (!this._weapon || !this._weapon.owner || !this._weapon.owner.linkedInputManager) {
|
||||
this.currentPitch = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let mousePosition = this._weapon.owner.linkedInputManager.secondaryLocation;
|
||||
let success = CinemachineCameraManager.instance.getScreenPointToWorldPosition(this._currentAim, mousePosition.x, mousePosition.y, this.mask);
|
||||
if (!success) {
|
||||
this.currentPitch = 0;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
this.calculateCurrentAim();
|
||||
}
|
||||
|
||||
calculateCurrentAim () {
|
||||
let refNode = this.node;//this._weapon?.getRotatingModel() || this.node;
|
||||
// debug aim line
|
||||
// GameManager.instance.drawLineByPos(this._currentAim, refNode.getWorldPosition());
|
||||
|
||||
MathUtil.convertToNodeSpace(_tempVec3, this._currentAim, refNode);
|
||||
if (_tempVec3.length() < this.aimRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
_tempVec3.normalize();
|
||||
// vector rotate begin from z axis to x axis angle
|
||||
/*
|
||||
z
|
||||
/ \
|
||||
|
|
||||
x /____|_____
|
||||
\ |
|
||||
|
|
||||
*/
|
||||
let addYaw = MathUtil.radiansToDegrees(Math.atan2(_tempVec3.x, _tempVec3.z));
|
||||
let addPitch = -MathUtil.radiansToDegrees(Math.atan2(_tempVec3.y, _tempVec3.z));
|
||||
if (Math.abs(addYaw) > this.pitchWhenYawInRange) addPitch = 0;
|
||||
|
||||
this.currentYaw = MathUtil.clampDegrees(this.currentYaw + addYaw);
|
||||
//if (this._weapon!.owner!.controlType == ControlType.TopDown) {
|
||||
// this.currentPitch = 0;
|
||||
//} else {
|
||||
this.currentPitch = MathUtil.clamp(MathUtil.clampDegrees(this.currentPitch + addPitch), this.minPitch, this.maxPitch);
|
||||
//}
|
||||
|
||||
let aimReady = this.aimReady(addYaw, addPitch);
|
||||
if (aimReady) {
|
||||
this.aimWasReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
aimReady (addYaw:number, addPitch:number) {
|
||||
return Math.abs(addYaw) < this.aimReadyValue && Math.abs(addPitch) < this.aimReadyValue;
|
||||
}
|
||||
|
||||
earlyProcess (dt:number) {
|
||||
}
|
||||
|
||||
process (dt:number) {
|
||||
this.updateCurrentAim();
|
||||
this.determineWeaponRotation();
|
||||
}
|
||||
|
||||
lateProcess (dt: number) {
|
||||
}
|
||||
|
||||
determineWeaponRotation () {
|
||||
if (this.yawRotateType != AimRotateType.ByWeapon && this.pitchRotateType != AimRotateType.ByWeapon) {
|
||||
return;
|
||||
}
|
||||
|
||||
let yaw = 0; let
|
||||
pitch = 0;
|
||||
if (this.yawRotateType == AimRotateType.ByWeapon) {
|
||||
yaw = this.currentYaw;
|
||||
}
|
||||
|
||||
if (this.pitchRotateType == AimRotateType.ByWeapon) {
|
||||
pitch = this.currentPitch;
|
||||
}
|
||||
|
||||
let node = this.node; //this._weapon?.getRotatingModel() || this.node;
|
||||
node.getRotation(_tempQuat);
|
||||
Quat.fromEuler(this._lookRotation, pitch, yaw, 0);
|
||||
Quat.multiply(this._lookRotation, _tempQuat, this._lookRotation);
|
||||
/*
|
||||
if (this._weapon) {
|
||||
this._weapon.rotateWeapon(this._lookRotation);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
11
assets/scripts/core/ik/weapon-aim.ts.meta
Normal file
11
assets/scripts/core/ik/weapon-aim.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "6da2baca-5114-4330-8c61-97ee4d67ceef",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user