~nasser/lospec-2

8f385f2319133db47499de41bbb1298d79f6f03d — Ramsey Nasser 6 months ago 732e7c3
Progress
A blend/art.blend => blend/art.blend +0 -0
A blend/art.blend1 => blend/art.blend1 +0 -0
A blend/art.glb => blend/art.glb +0 -0
A blend/level.blend => blend/level.blend +0 -0
A blend/level.blend1 => blend/level.blend1 +0 -0
A gltf/art.glb => gltf/art.glb +0 -0
A gltf/level.glb => gltf/level.glb +0 -0
A gltf/sheep.glb => gltf/sheep.glb +0 -0
M index.html => index.html +1 -1
@@ 3,7 3,7 @@
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lospec-2</title>
    <title>SHEEP</title>
    <meta name="description" content="undefined">
</head>
<body>

M main.js => main.js +278 -113
@@ 2,6 2,7 @@ import * as coro from "@ajeeb/coroutines"
import Input from "@ajeeb/input"
import time from "@ajeeb/input/time"
import * as THREE from "three"
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'


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

import kitty from './kitty.png?url'
import level from './gltf/level.glb?url'
import art from './gltf/art.glb?url'

const SCHED = new coro.Schedule()
const scene = new THREE.Scene()


@@ 31,6 34,8 @@ camera.lookAt(scene.position);
camera.scale.y = -1

const renderer = new THREE.WebGLRenderer()
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)

renderer.domElement.style.imageRendering = "pixelated"


@@ 98,8 103,26 @@ new THREE.TextureLoader().load(ls16.palette, palette => {
    composer.addPass(pass)
})

scene.add(new THREE.DirectionalLight())
scene.add(new THREE.AmbientLight())
const sun = new THREE.DirectionalLight('white', 1.5)
sun.position.set(-50, 100, 70)
// SCHED.add(function* () {
//     while(true) {
//         sun.position.y += INPUT.now.time.delta
//         sun.lookAt(new THREE.Vector3(0, 0, 0))
//         yield
//     }
// })
sun.castShadow = true
sun.shadow.normalBias = 10
sun.shadow.camera.left = -500
sun.shadow.camera.right = 500
sun.shadow.camera.top = -500
sun.shadow.camera.bottom = 500
scene.add(sun)
scene.add(new THREE.AmbientLight('white', 1))

const helper = new THREE.CameraHelper(sun.shadow.camera)
// scene.add(helper)

function toVector3(v2) {
    return new THREE.Vector3(v2.x, 0, v2.y)


@@ 115,45 138,105 @@ function drawPeg(position, color = 'white', size = 2) {
}

const goalZones = []
goalZones.push({
    box: new THREE.Box2(new THREE.Vector2(-190, -240), new THREE.Vector2(190, -60)),
    complete: false
})
// goalZones.push({
//     box: new THREE.Box2(new THREE.Vector2(-190, -240), new THREE.Vector2(190, -60)),
//     complete: false
// })

const velocityZones = []
velocityZones.push({
    box: new THREE.Box2(new THREE.Vector2(-40, -100), new THREE.Vector2(40, -20)),
    velocity: new THREE.Vector2(0, -1).normalize()
})
velocityZones.push({
    box: new THREE.Box2(new THREE.Vector2(-80, -50), new THREE.Vector2(-40, -20)),
    velocity: new THREE.Vector2(1, 0).normalize()
})
velocityZones.push({
    box: new THREE.Box2(new THREE.Vector2(-80 + 120, -50), new THREE.Vector2(-40 + 120, -20)),
    velocity: new THREE.Vector2(-1, 0).normalize()
// velocityZones.push({
//     box: new THREE.Box2(new THREE.Vector2(-40, -100), new THREE.Vector2(40, -20)),
//     velocity: new THREE.Vector2(0, -1).normalize()
// })
// velocityZones.push({
//     box: new THREE.Box2(new THREE.Vector2(-80, -50), new THREE.Vector2(-40, -20)),
//     velocity: new THREE.Vector2(1, 0).normalize()
// })
// velocityZones.push({
//     box: new THREE.Box2(new THREE.Vector2(-80 + 120, -50), new THREE.Vector2(-40 + 120, -20)),
//     velocity: new THREE.Vector2(-1, 0).normalize()
// })

const boundaries = []
// function makeTerrainPath(path) {
//     for (let i = 0; i < path.length - 1; i++)
//         boundaries.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],
// ])

new GLTFLoader().load(level, levelScene => {
    console.log('level', levelScene.scene)
    levelScene.scene.traverse(object => {
        if (object.userData.boundary) {
            const coordinates = object.geometry.attributes.position.array

            const from = new THREE.Vector2(coordinates[0], coordinates[2])
            const to = new THREE.Vector2()
            for (let i = 3; i < coordinates.length; i += 3) {
                to.x = coordinates[i + 0]
                to.y = coordinates[i + 2]
                boundaries.push({ from: from.clone(), to: to.clone() })
                from.copy(to)
            }
        } else if (object.userData.velocity) {
            velocityZones.push({
                box: new THREE.Box2(
                    new THREE.Vector2(object.position.x - object.scale.x, object.position.z - object.scale.z),
                    new THREE.Vector2(object.position.x + object.scale.x, object.position.z + object.scale.z),
                ),
                velocity: new THREE.Vector2(object.userData.direction[0], object.userData.direction[1]).normalize()
            })
        } else if (object.userData.goal) {
            goalZones.push({
                box: new THREE.Box2(
                    new THREE.Vector2(object.position.x - object.scale.x, object.position.z - object.scale.z),
                    new THREE.Vector2(object.position.x + object.scale.x, object.position.z + object.scale.z),
                )
            })
        } else if (object.parent === levelScene.scene) {
            const o = object.clone()
            o.castShadow = true
            o.receiveShadow = true
            o.traverse(oo => {
                if (oo.isMesh) {
                    oo.castShadow = true
                    oo.receiveShadow = true
                }
            })
            console.log('add', o)
            scene.add(o)
        } else {
            console.log('skip', object)
        }
    })
})

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],
])
let sheepMesh = null
let dogMesh = null

new GLTFLoader().load(art, artScene => {
    sheepMesh = artScene.scene.getObjectByName('sheep').clone()
    dogMesh = artScene.scene.getObjectByName('dog').clone()
    console.log('dogMesh', dogMesh)
    sheepMesh.scale.set(4, 4, 4)
    sheepMesh.castShadow = true
    sheepMesh.receiveShadow = true
    dogMesh.scale.set(4, 4, 4)
})


SCHED.add(function* () {


@@ 163,23 246,25 @@ SCHED.add(function* () {
    const orange = new THREE.Color(0xff8f00)
    const center = new THREE.Vector2()
    while (true) {
        for (const segment of terrain)
        // yield
        // continue
        for (const segment of boundaries)
            GIZMOS.line(toVector3(segment.from), toVector3(segment.to), blue)
        // for (const zone of velocityZones) {
        //     GIZMOS.line(toVector3(zone.box.min), toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), orange)
        //     GIZMOS.line(toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), toVector3(zone.box.max), orange)
        //     GIZMOS.line(toVector3(zone.box.max), toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), orange)
        //     GIZMOS.line(toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), toVector3(zone.box.min), orange)
        //     zone.box.getCenter(center)
        //     GIZMOS.line(toVector3(center), toVector3(center.clone().addScaledVector(zone.velocity, 20)), orange)
        // }
        // for (const zone of goalZones) {
        //     const color = zone.complete ? green : darkGreen
        //     GIZMOS.line(toVector3(zone.box.min), toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), color)
        //     GIZMOS.line(toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), toVector3(zone.box.max), color)
        //     GIZMOS.line(toVector3(zone.box.max), toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), color)
        //     GIZMOS.line(toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), toVector3(zone.box.min), color)
        // }
        for (const zone of velocityZones) {
            GIZMOS.line(toVector3(zone.box.min), toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), green)
            GIZMOS.line(toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), toVector3(zone.box.max), green)
            GIZMOS.line(toVector3(zone.box.max), toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), green)
            GIZMOS.line(toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), toVector3(zone.box.min), green)
            zone.box.getCenter(center)
            GIZMOS.line(toVector3(center), toVector3(center.clone().addScaledVector(zone.velocity, 20)), green)
        }
        for (const zone of goalZones) {
            const color = zone.complete ? green : orange
            GIZMOS.line(toVector3(zone.box.min), toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), color)
            GIZMOS.line(toVector3(new THREE.Vector2(zone.box.min.x, zone.box.max.y)), toVector3(zone.box.max), color)
            GIZMOS.line(toVector3(zone.box.max), toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), color)
            GIZMOS.line(toVector3(new THREE.Vector2(zone.box.max.x, zone.box.min.y)), toVector3(zone.box.min), color)
        }
        yield
    }
})


@@ 193,9 278,9 @@ const dog = {
}

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


@@ 208,72 293,94 @@ function getSheepNear(p, except = null, threshold = 10) {
    return near
}

scene.background = new THREE.Color(0x260a34)

const left = new THREE.Vector3(0, 0, -1)

function* oneSheep(sheep) {
    const forward = new THREE.Vector3()
    const matrix = new THREE.Matrix4()
    while (!sheepMesh) yield
    const mesh = sheepMesh.clone()
    scene.add(mesh)
    const line = new THREE.Line3()
    const closest = new THREE.Vector3()

SCHED.add(function* sheep() {
    while (true) {
        for (const sheep of sheepies) {
            let color = 'white'
            // flocking
            const neighborhood = getSheepNear(sheep.position, sheep, 20)
            if (neighborhood.length > 0) {
                const centroid = new THREE.Vector2()
                const avoidOthers = new THREE.Vector2()
                const averageVelocity = new THREE.Vector2()
                for (const neighbor of neighborhood) {
                    centroid.add(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(avoidOthers.multiplyScalar(2))
                sheep.velocity.add(averageVelocity.addScaledVector(sheep.velocity, 4))

                sheep.velocity.normalize()
                sheep.position.addScaledVector(sheep.velocity, 0.1)
                color = 'white'
        const oldPosition = new THREE.Vector3(sheep.position.x, 0, sheep.position.y)

        let turnSpeed = 0.01
        // flocking
        const neighborhood = getSheepNear(sheep.position, sheep, 20)
        if (neighborhood.length > 0) {
            const centroid = new THREE.Vector2()
            const avoidOthers = new THREE.Vector2()
            const averageVelocity = new THREE.Vector2()
            for (const neighbor of neighborhood) {
                centroid.add(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(avoidOthers.multiplyScalar(2))
            sheep.velocity.add(averageVelocity.addScaledVector(sheep.velocity, 4))

            // 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'
            }
            sheep.velocity.normalize()
            sheep.position.addScaledVector(sheep.velocity, 0.1)
        }

            // zone
            for (const zone of velocityZones) {
                if (zone.box.containsPoint(sheep.position)) {
                    sheep.velocity.addScaledVector(zone.velocity, 2)
                    // color = 0x007062
                }
            }
        // 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))
            turnSpeed = 0.1
        }

            // 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'
                }
        // zone
        for (const zone of velocityZones) {
            if (zone.box.containsPoint(sheep.position)) {
                sheep.velocity.addScaledVector(zone.velocity, 2)
                turnSpeed = 0.1
            }
        }

            sheep.position.addScaledVector(sheep.velocity, 0.1)
            drawPeg(sheep.position, color, 8)
        // avoid terrain
        for (const segment of boundaries) {
            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))
                turnSpeed = 0.1
            }
        }

        sheep.position.addScaledVector(sheep.velocity, 0.1)

        // drawPeg(sheep.position, 'white', 8)
        mesh.getWorldDirection(forward)
        const newPosition = new THREE.Vector3(sheep.position.x, 0, sheep.position.y)
        matrix.lookAt(newPosition, mesh.position, mesh.up)

        const displacement = oldPosition.distanceTo(newPosition)

        mesh.position.lerp(newPosition, 0.1)
        mesh.quaternion.slerp(new THREE.Quaternion().setFromRotationMatrix(matrix), displacement * 0.1)
        yield
    }
})
}

for (const sheep of sheepies) {
    SCHED.add(oneSheep(sheep))
}


SCHED.add(function* sheep() {
    while (true) {


@@ 329,13 436,71 @@ SCHED.add(function* () {
    }
})

function intersect(p1, p2, p3, p4, target) {
    const s1_x = p2.x - p1.x;
    const s1_y = p2.y - p1.y;
    const s2_x = p4.x - p3.x;
    const s2_y = p4.y - p3.y;

    const s = (-s1_y * (p1.x - p3.x) + s1_x * (p1.y - p3.y)) / (-s2_x * s1_y + s1_x * s2_y);
    const t = (s2_x * (p1.y - p3.y) - s2_y * (p1.x - p3.x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
        const intX = p1.x + (t * s1_x);
        const intY = p1.y + (t * s1_y);
        target.set(intX, intY)
        return true
    }
    return false
}

SCHED.add(function* dogMovement() {
    while (!dogMesh) yield
    const matrix = new THREE.Matrix4()
    const mesh = dogMesh.clone()
    mesh.receiveShadow = true
    scene.add(mesh)
    const line = new THREE.Line3()
    const closest = new THREE.Vector3()
    const testPosition = new THREE.Vector2()

    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)

        testPosition.lerp(cursor.position, INPUT.now.time.delta * 8)

        // avoid terrain
        for (const segment of boundaries) {
            if (intersect(segment.from, segment.to, dog.position, cursor.position, cursor.position)) {
                // console.log('bump')
                // cursor.position = segment.to
            }
            // line.set(toVector3(segment.from), toVector3(segment.to))
            // line.closestPointToPoint(toVector3(mesh.position), true, closest)
            // const closest2 = toVector2(closest)
            // const segmentDistance = mesh.position.distanceTo(closest2)
            // if (segmentDistance < 10) {
            //     const avoidSegment = new THREE.Vector2()
            //     avoidSegment.add(mesh.position.clone().sub(closest2).multiplyScalar(10 / segmentDistance))
            // }
        }

        dog.position.lerp(cursor.position, INPUT.now.time.delta * 8)
        // drawPeg(dog.position, 0xff8f00, 12)
        // drawPeg(cursor.position, 0xff8f00, 12)
        const newPosition = new THREE.Vector3(dog.position.x, 0, dog.position.y)
        // mesh.lookAt(newPosition)
        matrix.lookAt(newPosition, mesh.position, mesh.up)
        mesh.quaternion.slerp(new THREE.Quaternion().setFromRotationMatrix(matrix), 0.1)
        mesh.position.lerp(newPosition, 0.1)
        camera.position.setFromSphericalCoords(
            1000,
            Math.PI / 3,
            Math.PI / 4
        )
        camera.position.x += mesh.position.x
        camera.position.z += mesh.position.z
        yield
    }
})