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