@@ 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()
@@ 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