~nasser/lospec-2

379a568b2c665b826fde22f626b01a589da5fc4a — Ramsey Nasser 7 months ago 6d8b211
Add terrain, other progress
3 files changed, 201 insertions(+), 29 deletions(-)

M gizmos.js
M main.js
A pointer.js
M gizmos.js => gizmos.js +1 -0
@@ 1,3 1,4 @@
// TODO add this to @ajeeb/three
import * as THREE from "three"

export default class {

M main.js => main.js +134 -29
@@ 7,6 7,8 @@ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass'
import Gizmos from "./gizmos"
// import pointer from "./pointer"
import keyboard from "@ajeeb/input/dom/keyboard"

import ls16 from "./ls16"
// import * as Tone from 'tone'


@@ 15,10 17,6 @@ import './style.css'
import palettePass from "./palette-pass"

const SCHED = new coro.Schedule()
const INPUT = new Input([
    time
])

const scene = new THREE.Scene()

const camera = new THREE.OrthographicCamera()


@@ 54,6 52,35 @@ function resize() {
window.addEventListener('resize', resize)
resize()

function gamepad(now) {
    const gamepad = {
        left: now.keyboard.held.has('ArrowLeft'),
        right: now.keyboard.held.has('ArrowRight'),
        up: now.keyboard.held.has('ArrowUp'),
        down: now.keyboard.held.has('ArrowDown'),
        a: now.keyboard.held.has('KeyZ'),
        b: now.keyboard.held.has('KeyX'),
        start: now.keyboard.held.has('Enter'),
        select: now.keyboard.held.has('Space'),
    }
    return { gamepad }
}

function axes(now) {
    const axes = {
        horizontal: -now.gamepad.left + -now.gamepad.up + now.gamepad.right + now.gamepad.down,
        vertical: -now.gamepad.right + -now.gamepad.up + now.gamepad.left + now.gamepad.down
    }
    return { axes }
}

const INPUT = new Input([
    time,
    keyboard,
    gamepad,
    axes
])

scene.add(new THREE.GridHelper(500, 8))
// new OrbitControls(camera, renderer.domElement)



@@ 72,67 99,145 @@ new THREE.TextureLoader().load(ls16.palette, palette => {
scene.add(new THREE.DirectionalLight())
scene.add(new THREE.AmbientLight())

function drawSheep(position, color = 'white', size = 8) {
function toVector3(v2) {
    return new THREE.Vector3(v2.x, 0, v2.y)
}

function toVector2(v2) {
    return new THREE.Vector2(v2.x, v2.z)
}

function drawPeg(position, color = 'white', size = 2) {
    const v = new THREE.Vector3(Math.floor(position.x), 0, Math.floor(position.y))
    GIZMOS.line(v, v.clone().add(new THREE.Vector3(0, size, 0)), new THREE.Color(color))
}

function line2d(a, b, color) {
    GIZMOS.line(new THREE.Vector3(a.x, 0, a.y), new THREE.Vector3(b.x, 0, b.y), color)
const terrain = []
function makeTerrainPath(path) {
    for (let i = 0; i < path.length - 1; i++)
        terrain.push({ from: new THREE.Vector2(...path[i]), to: new THREE.Vector2(...path[i + 1]) })
}
makeTerrainPath([
    [50, -50],
    [200, -50],
    [200, -250],
    [-200, -250],
    [-200, -50],
    [-50, -50],
])

makeTerrainPath([
    [40, 500],
    [40, 200],
    [210, 10],
    [310, 10],
])


SCHED.add(function* () {
    const sheepies = []
    for (let i = 0; i < 200; i++) {
        sheepies.push({
            position: new THREE.Vector2(Math.random() * 80, Math.random() * 80),
            velocity: new THREE.Vector2()
        })
    const blue = new THREE.Color('blue')
    while (true) {
        for (const segment of terrain) {
            GIZMOS.line(toVector3(segment.from), toVector3(segment.to), blue)
        }
        yield
    }
})

    function getSheepNear(p, except = null, threshold = 10) {
        const near = []
        for (const sheep of sheepies)
            if (sheep !== except && sheep.position.distanceTo(p) < threshold)
                near.push(sheep)
        return near
    }
const cursor = {
    position: new THREE.Vector2()
}

const dog = {
    position: new THREE.Vector2()
}

const sheepies = []
for (let i = 0; i < 200; i++) {
    sheepies.push({
        position: new THREE.Vector2(Math.random() * 80, Math.random() * 80),
        velocity: new THREE.Vector2()
    })
}

function getSheepNear(p, except = null, threshold = 10) {
    const near = []
    for (const sheep of sheepies)
        if (sheep !== except && sheep.position.distanceTo(p) < threshold)
            near.push(sheep)
    return near
}

    const cohesionFactor = 0.005

SCHED.add(function* sheep() {
    while (true) {
        for (const sheep of sheepies) {
            let color = 'gray'
            // flocking
            const neighborhood = getSheepNear(sheep.position, sheep, 20)
            if (neighborhood.length > 0) {
                const centroid = new THREE.Vector2()
                const avoidMovement = new THREE.Vector2()
                const avoidOthers = new THREE.Vector2()
                const averageVelocity = new THREE.Vector2()
                for (const neighbor of neighborhood) {
                    centroid.add(neighbor.position)
                    avoidMovement.add(sheep.position.clone().sub(neighbor.position).multiplyScalar(2 / sheep.position.distanceTo(neighbor.position)))
                    avoidOthers.add(sheep.position.clone().sub(neighbor.position).multiplyScalar(2 / sheep.position.distanceTo(neighbor.position)))
                    averageVelocity.add(neighbor.velocity)
                }
                averageVelocity.divideScalar(neighborhood.length)
                centroid.divideScalar(neighborhood.length)
                sheep.velocity.add(centroid.clone().sub(sheep.position).multiplyScalar(1))
                sheep.velocity.add(avoidMovement.multiplyScalar(2))
                sheep.velocity.add(avoidOthers.multiplyScalar(2))
                sheep.velocity.add(averageVelocity.addScaledVector(sheep.velocity, 4))

                sheep.velocity.normalize()
                sheep.position.addScaledVector(sheep.velocity, 0.1)
                drawSheep(sheep.position)
                // line2d(sheep.position, centroid, 'yellow')
            } else {
                sheep.position.add(sheep.velocity.clone().multiplyScalar(0.1))
                drawSheep(sheep.position, 'gray')
                color = 'white'
            }

            // avoid dog
            const dogDistance = sheep.position.distanceTo(dog.position)
            if (dogDistance < 50) {
                const avoidDog = new THREE.Vector2()
                avoidDog.add(sheep.position.clone().sub(dog.position).multiplyScalar(1 / dogDistance))
                sheep.velocity.add(avoidDog.multiplyScalar(1))
                color = 'yellow'
            }

            // avoid terrain
            const line = new THREE.Line3()
            const closest = new THREE.Vector3()
            for (const segment of terrain) {
                line.set(toVector3(segment.from), toVector3(segment.to))
                line.closestPointToPoint(toVector3(sheep.position), true, closest)
                const closest2 = toVector2(closest)
                const segmentDistance = sheep.position.distanceTo(closest2)
                if (segmentDistance < 10) {
                    const avoidSegment = new THREE.Vector2()
                    avoidSegment.add(sheep.position.clone().sub(closest2).multiplyScalar(10 / segmentDistance))
                    sheep.velocity.add(avoidSegment.multiplyScalar(1))
                    color = 'red'
                }
            }

            sheep.position.addScaledVector(sheep.velocity, 0.1)
            drawPeg(sheep.position, color, 8)
        }
        yield
    }
})

SCHED.add(function* dogMovement() {
    while (true) {
        cursor.position.x += INPUT.now.axes.horizontal
        cursor.position.y += INPUT.now.axes.vertical
        dog.position.lerp(cursor.position, INPUT.now.time.delta)
        drawPeg(dog.position, 'orange', 12)
        drawPeg(cursor.position, 'green', 4)
        yield
    }
})

function tick() {
    requestAnimationFrame(tick)
    GIZMOS.reset()

A pointer.js => pointer.js +66 -0
@@ 0,0 1,66 @@
// TODO add this to @ajeeb/input/dom/pointer
export default class {
    constructor(element = document, startListening = true, stopPropagation = true) {
        this.element = element
        this.ndc = { x: 0, y: 0 }
        this.position = { x: 0, y: 0 }
        // TODO the pointer api captures a lot more information like pressure, tilt etc. we should expose that here.
        this.down = false
        this.held = false
        this.up = false

        this.pointermove = e => {
            this.position.x = e.clientX
            this.position.y = e.clientY
            this.ndc.x = (e.clientX / window.innerWidth) * 2 - 1;
            this.ndc.y = - (e.clientY / window.innerHeight) * 2 + 1;
        }

        this.pointerdown = e => {
            this.down = true
            this.held = true
        }

        this.pointerup = e => {
            this.down = false
            this.held = false
            this.up = true
        }

        this.blur = () => {
            this.down = false
            this.held = false
            this.up = false
        }

        if (startListening)
            this.startListening()
    }

    startListening() {
        document.addEventListener('pointermove', this.pointermove)
        document.addEventListener('pointerdown', this.pointerdown)
        document.addEventListener('pointerup', this.pointerup)
        window.addEventListener("blur", this.blur)
    }

    stopListening() {
        document.removeEventListener('pointermove', this.pointermove)
        document.removeEventListener('pointerdown', this.pointerdown)
        document.removeEventListener('pointerup', this.pointerup)
        window.removeEventListener("blur", this.blur)
    }

    collect() {
        const pointer = {
            ndc: { ...this.ndc },
            position: { ...this.position },
            held: this.held,
            down: this.down,
            up: this.up
        }
        this.down = false
        this.up = false
        return { pointer }
    }
}
\ No newline at end of file