On this page

# Turtle3D.js

    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

Type anything to view search result