-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathTurtle3D.js
255 lines (236 loc) · 7.79 KB
/
Turtle3D.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import Turtle from './Turtle.js'
import { Object3D } from '../vendor/Object3D.js'
import * as util from './utils.js'
const { checkArg, checkArgs } = util
/**
* Class Turtle3D subclasses {@link Turtles}, adding 3D methods using
* Three.js's Object3D module.
* See [NetLogo](https://ccl.northwestern.edu/netlogo/docs/3d.html)
* who's 3D semantics we follow.
*
* Just as with Turtle, you do not call `new Turtle3D()`,
* instead class Turtles creates Turtle3D instances via
* {@link Model} modifying the Turtles/Turtle3D initialization.
*
* Again, class Turtles is a factory for all of it's Turtle3D instances.
* So *don't* do this:
*/
class Turtle3D extends Turtle {
constructor() {
super()
// needed to avoid Turtle z setting. Remove after fixing z gatters
this.obj3d = new Object3D()
}
newInstance(agentProto) {
const insstance = super.newInstance(agentProto)
insstance.obj3d = new Object3D()
insstance.obj3d.rotation.order = 'ZYX'
insstance.reset()
return insstance
}
/**
* Resets this turtle's position, rotation and heading all to 0's
*/
reset() {
this.obj3d.position.set(0, 0, 0)
this.obj3d.rotation.set(0, 0, 0)
this.heading = 0
}
/**
* Set's this turtle's 3D, x y z, position
*
* @param {number} x float for x position
* @param {number} y float for y position
* @param {number} z float for z position
*/
setxyz(x, y, z) {
// checkArgs(arguments)
super.setxy(x, y, z)
}
getxyz() {
return this.obj3d.position.toArray()
}
setRotation(x, y, z) {
// checkArgs(arguments)
this.obj3d.rotation.set(x, y, z)
// super/this.theta = this.obj3d.rotation.z ????
}
getRotation() {
const { x, y, z } = this.obj3d.rotation // .reorder('ZYX')
return [x, y, z]
}
getThetaPhiPsi() {
return this.getRotation().reverse()
}
getHeadingPitchRoll() {
const [psi, phi, theta] = this.getRotation()
const heading = util.radToHeading(theta)
const pitch = util.radToDeg(-phi)
const roll = util.radToDeg(psi)
return [heading, pitch, roll]
// return [this.heading, this.pitch, this.roll] // ????
}
getDxDyDz() {
return [this.dx, this.dy, this.dz]
}
// REMIND: temporary.
// handleEdge(x, y, z) {
// super.handleEdge(x, y, z)
// this.setxyz(this.x, this.y, this.z)
// }
get x() {
return this.obj3d.position.x
}
set x(d) {
// checkArg(d)
this.obj3d.position.x = d
}
get y() {
return this.obj3d.position.y
}
set y(d) {
// checkArg(d)
this.obj3d.position.y = d
}
get z() {
return this.obj3d.position.z
}
set z(d) {
// checkArg(d)
// this.obj3d.position.z = d
// This test is needed due to z getter called by super initialization.
if (this.obj3d) this.obj3d.position.z = d
}
// Trap super's setting of theta
get theta() {
// util.warn('theta is deprecated, use heading instead')
return this.obj3d.rotation.z
}
set theta(rad) {
// checkArg(rad)
// util.warn('theta is deprecated, use heading instead')
if (this.obj3d) this.obj3d.rotation.z = rad
}
get heading() {
return this.model.fromRads(this.obj3d.rotation.z)
}
set heading(angle) {
// checkArg(angle)
this.obj3d.rotation.z = this.model.toRads(angle)
}
get pitch() {
// return -this.model.fromRads(this.obj3d.rotation.y)
// return -this.model.fromAngleRads(this.obj3d.rotation.y)
return -this.model.fromAngleRads(this.obj3d.rotation.y)
}
set pitch(angle) {
// checkArg(angle)
// this.obj3d.rotation.y = -this.model.toRads(angle)
// this.obj3d.rotation.y = -this.model.toAngleRads(angle)
this.obj3d.rotation.y = -this.model.toAngleRads(angle)
}
get roll() {
// return this.model.fromRads(this.obj3d.rotation.x)
// return this.model.fromAngleRads(this.obj3d.rotation.x)
return this.model.fromAngleRads(this.obj3d.rotation.x)
}
set roll(angle) {
// checkArg(angle)
// this.obj3d.rotation.x = this.model.toRads(angle)
// this.obj3d.rotation.x = this.model.toAngleRads(angle)
this.obj3d.rotation.x = this.model.toAngleRads(angle)
}
// Move along the turtle's X axis
forward(d) {
// checkArg(d)
const p0 = this.patch
this.obj3d.translateX(d)
super.checkXYZ(p0)
// let [x, y, z] = this.getxyz()
// super.setxy(x, y, z)
}
// Incremental rotation around given axis
right(angle) {
this.left(-angle)
// this.obj3d.rotateZ(-this.model.toAngleRads(angle))
// this.theta = this.obj3d.rotation.z
}
left(angle) {
// checkArg(angle)
this.obj3d.rotateZ(this.model.toAngleRads(angle))
// this.right(-angle)
}
tiltUp(angle) {
// this.obj3d.rotateY(-this.model.toAngleRads(angle))
this.tiltDown(-angle)
}
tiltDown(angle) {
// checkArg(angle)
this.obj3d.rotateY(this.model.toAngleRads(angle))
// this.tiltUp(-angle)
}
rollRight(angle) {
// checkArg(angle)
this.obj3d.rotateX(this.model.toAngleRads(angle))
}
rollLeft(angle) {
this.rollRight(-angle)
}
facexyz(x1, y1, z1) {
// checkArgs(arguments)
// const headingTowards = this.model.toRads(this.towardsXY(x1, y1))
// const pitchTowards = this.model.toRads(this.towardsPitchXYZ(x1, y1, z1)
const headingTowards = this.towardsXY(x1, y1)
const pitchTowards = this.towardsPitchXYZ(x1, y1, z1)
// const roll = this.roll
// this.obj3d.rotation.set(0, 0, 0)
this.heading = headingTowards
this.pitch = pitchTowards
// this.roll = roll
}
face(agent) {
// checkArg(agent, 'object')
const { x, y, z } = agent
this.facexyz(x, y, z)
}
towardsPitchXYZ(x1, y1, z1) {
// checkArgs(arguments)
const [x, y, z] = this.getxyz()
const [dx, dy, dz] = [x1 - x, y1 - y, z1 - z]
const xyhypot = Math.hypot(dx, dy)
const pitchRads = Math.atan2(dz, xyhypot)
return this.model.fromAngleRads(pitchRads)
}
towardsPitch(agent) {
// checkArg(agent, 'object')
const { x, y, z } = agent
this.towardsPitchXYZ(x, y, z)
}
distance(agent) {
// checkArg(agent, 'object')
const { x, y, z } = agent
return this.distanceXYZ(x, y, z)
}
distanceXYZ(x1, y1, z1) {
// checkArgs(arguments)
const { x, y, z } = this
return util.distance3(x, y, z, x1, y1, z1)
}
// From https://ccl.northwestern.edu/netlogo/docs/
// Note: dx is simply the sine of the turtle's heading, and dy is simply the cosine. (If this is the reverse of what you expected, it's because in NetLogo a heading of 0 is north and 90 is east, which is the reverse of how angles are usually defined in geometry.)
// Note: In earlier versions of NetLogo, these primitives were used in many situations where the new patch-ahead primitive is now more appropriate.
// NOTE: dz is simply the sine of the turtle's pitch. Both dx and dy have changed in this case. So, dx = cos(pitch) * sin(heading) and dy = cos(pitch) * cos(heading).
get dx() {
const { y: pitch, z: heading } = this.obj3d.rotation
return Math.cos(pitch) * Math.cos(heading)
}
get dy() {
const { y: pitch, z: heading } = this.obj3d.rotation
return Math.cos(pitch) * Math.sin(heading)
}
get dz() {
const pitch = this.obj3d.rotation.y
return Math.sin(pitch)
}
}
export default Turtle3D