-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathsimple-navmesh-constraint.js
173 lines (150 loc) · 5.38 KB
/
simple-navmesh-constraint.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* global AFRAME, THREE */
AFRAME.registerComponent('simple-navmesh-constraint', {
schema: {
enabled: {
default: true
},
navmesh: {
default: ''
},
fall: {
default: 0.5
},
height: {
default: 1.6
},
exclude: {
default: ''
},
xzOrigin: {
default: ''
}
},
init: function () {
this.onSceneUpdated = this.onSceneUpdated.bind(this);
this.el.sceneEl.addEventListener('child-attached', this.onSceneUpdated);
this.el.sceneEl.addEventListener('child-detached', this.onSceneUpdated);
this.objects = [];
this.excludes = [];
},
remove: function () {
this.el.sceneEl.removeEventListener('child-attached', this.onSceneUpdated);
this.el.sceneEl.removeEventListener('child-detached', this.onSceneUpdated);
},
onSceneUpdated: function (evt) {
// We already have an update on the way
if (this.entitiesChanged) { return; }
// Don't bother updating if the entity is not relevant to us
if (evt.detail.el.matches(this.data.navmesh) || evt.detail.el.matches(this.data.exclude)) {
this.entitiesChanged = true;
}
},
updateNavmeshEntities: function () {
this.objects.length = 0;
this.excludes.length = 0;
if (this.data.navmesh.length > 0) {
for (const navmesh of document.querySelectorAll(this.data.navmesh)) {
this.objects.push(navmesh.object3D);
}
}
if (this.objects.length === 0) {
console.warn('simple-navmesh-constraint: Did not match any elements');
} else if (this.data.exclude.length > 0) {
for (const excluded of document.querySelectorAll(this.data.exclude)) {
this.objects.push(excluded.object3D);
this.excludes.push(excluded);
}
}
this.entitiesChanged = false;
},
update: function () {
this.lastPosition = null;
this.xzOrigin = this.data.xzOrigin ? this.el.querySelector(this.data.xzOrigin) : this.el;
this.updateNavmeshEntities();
},
tick: (function () {
const nextPosition = new THREE.Vector3();
const tempVec = new THREE.Vector3();
const scanPattern = [
[0,1], // Default the next location
[0,0.5], // Check that the path to that location was fine
[30,0.4], // A little to the side shorter range
[-30,0.4], // A little to the side shorter range
[60,0.2], // Moderately to the side short range
[-60,0.2], // Moderately to the side short range
[80,0.06], // Perpendicular very short range
[-80,0.06], // Perpendicular very short range
];
const down = new THREE.Vector3(0,-1,0);
const raycaster = new THREE.Raycaster();
const gravity = -1;
const maxYVelocity = 0.5;
const results = [];
let yVel = 0;
let firstTry = true;
return function tick(time, delta) {
if (this.data.enabled === false) return;
if (this.entitiesChanged) {
this.updateNavmeshEntities();
}
if (this.lastPosition === null) {
firstTry = true;
this.lastPosition = new THREE.Vector3();
this.xzOrigin.object3D.getWorldPosition(this.lastPosition);
if (this.data.xzOrigin) this.lastPosition.y -= this.xzOrigin.object3D.position.y;
}
const el = this.el;
if (this.objects.length === 0) return;
this.xzOrigin.object3D.getWorldPosition(nextPosition);
if (this.data.xzOrigin) nextPosition.y -= this.xzOrigin.object3D.position.y;
if (nextPosition.distanceTo(this.lastPosition) <= 0.01) return;
let didHit = false;
// So that it does not get stuck it takes as few samples around the user and finds the most appropriate
scanPatternLoop:
for (const [angle, distance] of scanPattern) {
tempVec.subVectors(nextPosition, this.lastPosition);
tempVec.applyAxisAngle(down, angle*Math.PI/180);
tempVec.multiplyScalar(distance);
tempVec.add(this.lastPosition);
tempVec.y += maxYVelocity;
tempVec.y -= this.data.height;
raycaster.set(tempVec, down);
raycaster.far = this.data.fall > 0 ? this.data.fall + maxYVelocity : Infinity;
raycaster.intersectObjects(this.objects, true, results);
if (results.length) {
// If it hit something we want to avoid then ignore it and stop looking
for (const result of results) {
if(this.excludes.includes(result.object.el)) {
results.splice(0);
continue scanPatternLoop;
}
}
const hitPos = results[0].point;
results.splice(0);
hitPos.y += this.data.height;
if (nextPosition.y - (hitPos.y - yVel*2) > 0.01) {
yVel += Math.max(gravity * delta * 0.001, -maxYVelocity);
hitPos.y = nextPosition.y + yVel;
} else {
yVel = 0;
}
tempVec.copy(hitPos);
this.xzOrigin.object3D.parent.worldToLocal(tempVec);
tempVec.sub(this.xzOrigin.object3D.position);
if (this.data.xzOrigin) tempVec.y += this.xzOrigin.object3D.position.y;
this.el.object3D.position.add(tempVec);
this.lastPosition.copy(hitPos);
didHit = true;
break;
}
}
if (didHit) {
firstTry = false;
}
if (!firstTry && !didHit) {
this.el.object3D.position.copy(this.lastPosition);
this.el.object3D.parent.worldToLocal(this.el.object3D.position);
}
}
}())
});