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