Rooms.mover = new function(){ var base = this base.room = null base.noclip = false var cursor = base.cursor = new Rect (0,0,0,0) var cursor_copy = new Rect (0,0,0,0) var wall_vec = new Rect (0,0,0,0) var intersect = new vec2(0,0) var origins = new Rect() origins.x = cursor.x origins.y = wall_vec.x base.init = function(){ base.bind() base.update(scene.camera) } base.bind = function(){ app.on("move", base.update) keys.on("backslash", function(e){ if ( ! (e.ctrlKey || e.metaKey) ) return base.noclip = ! base.noclip base.room = null app.movements.gravity( ! base.noclip ) }) } base.update = function(pos){ var intersect, collision, distance var radius = scene.camera.radius cam.y = pos.y // if we were in a room already.. if (base.room) { // check if we're still in the room if (base.room.rect.containsDisc(pos.x, pos.z, radius)) { cam.x = pos.x cam.z = pos.z return } distance = sqrt(pow(cam.x-pos.x, 2), pow(cam.z-pos.z, 2)) // check if we've breached one of the walls.. clamp position if so. // there are two algorithms for this now, ridiculously.. // - the first one is smoother, best for keyboard use // but if the distance travelled is greater than the min. distance from the wall, // then there is a possibility of ejection from the room. so is bad for phone/mousewheel. // - the second one determines fortuitously if we have breached any of the walls // however it can get jumpy if you run into a wall.. thus it is best for devices like phone or mousewheel // the benefit is you will never leave the room. if (base.noclip) { // in no-clip mode we walk through walls. } else if (distance < radius) { collision = base.room.collidesDisc(cam, pos, radius) if (collision) { cam.x = (collision & LEFT_RIGHT) ? base.room.rect.x.clampDisc(pos.x, radius) : pos.x cam.z = (collision & FRONT_BACK) ? base.room.rect.y.clampDisc(pos.z, radius) : pos.z return } } else { intersect = base.intersect(pos) if (intersect) { cam.x = intersect.a cam.z = intersect.b return } } // in this case, we appear to have left the room.. // $(".face.active").removeClass("active") Walls.clearBodyColor() base.room = null } // collision test failed, so update position cam.x = pos.x cam.z = pos.z // determine what room we are in now var intersects = Rooms.filter(function(r){ return r.rect.contains(pos.x, pos.z) }) // did we actually enter a room? if (intersects.length) { base.room = intersects[0] if (! base.noclip) { Walls.setBodyColor() } app.tube("change-room", { room: base.room }) } } base.intersect = function(pos){ var closest_intersect, closest_wall, new_t, wall_t, t, min_t = 1 cursor_copy.x.a = cursor.x.a = cam.x cursor_copy.x.b = cursor.x.b = cam.z cursor_copy.y.a = cursor.y.a = pos.x cursor_copy.y.b = cursor.y.b = pos.z cursor_copy.extend_ends(scene.camera.radius) origins.x = cursor_copy.x origins.y = wall_vec.x Walls.list.forEach(function(wall, i){ var actually_intersects = false, intersecting_face wall.get_points(wall_vec) t = perp(origins, wall_vec) / ( perp(cursor_copy, wall_vec) || 0.0000001 ) if ( min_t < t || t < 0 || 1 < t ) return intersect.a = cursor_copy.x.a + ( cursor_copy.y.a - cursor_copy.x.a ) * t intersect.b = cursor_copy.x.b + ( cursor_copy.y.b - cursor_copy.x.b ) * t if ( ! is_collinear( intersect, wall_vec ) ) return if (wall.side & LEFT_RIGHT) { intersecting_face = wall.surface.face_for_x(intersect.b) } else { intersecting_face = wall.surface.face_for_x(intersect.a) } actually_intersects = !! (intersecting_face && intersecting_face.y.a == 0) if (actually_intersects) { closest_intersect = intersect.clone() closest_wall = wall_vec.clone() min_t = t } }) if (closest_intersect) { var aw, len, dd aw = angle(closest_wall) wall_vec.assign(closest_wall) wall_vec.x.a -= scene.camera.radius * sin(aw) wall_vec.x.b += scene.camera.radius * cos(aw) wall_vec.y.a -= scene.camera.radius * sin(aw) wall_vec.y.b += scene.camera.radius * cos(aw) origins.x = cursor.x origins.y = wall_vec.x new_t = perp(origins, wall_vec) / ( perp(cursor, wall_vec) || 0.0000001 ) wall_t = perp(origins, cursor) / ( perp(wall_vec, cursor) || 0.0000001 ) intersect.a = cursor.x.a + ( cursor.y.a - cursor.x.a ) * new_t intersect.b = cursor.x.b + ( cursor.y.b - cursor.x.b ) * new_t // here compare len to the length of the wall in the direction we are travelling dd = dot2(diff(closest_wall), diff(cursor)) len = sqrt(dot(wall_vec, wall_vec)) if (dd > 0) { len *= 1-abs(wall_t) } else { len *= abs(wall_t) aw += PI } len = clamp(len, 0, (1-min_t) * sqrt(dot(cursor, cursor))) intersect.a += len * cos(aw) intersect.b += len * sin(aw) wall_vec.normalize() intersect.a = clamp(intersect.a, wall_vec.x.a, wall_vec.y.a) intersect.b = clamp(intersect.b, wall_vec.x.b, wall_vec.y.b) return intersect } else { return cursor.y } } function diff (v) { return new vec2(v.y.a - v.x.a, v.y.b - v.x.b) } function angle (va) { return atan2(va.y.b - va.x.b, va.y.a - va.x.a) } function angle2 (pa, pb) { return atan2(pb.b - pa.b, pb.a - pa.a) } function normal (va) { return atan2(va.x.a - va.y.a, va.y.b - va.x.b) } function dot (va, vb) { return (va.y.a - va.x.a) * (vb.y.a - vb.x.a) + (va.y.b - va.x.b) * (vb.y.b - vb.x.b) } function dot2 (pa, pb) { return pa.a * pb.a + pa.b * pb.b } function perp (va, vb) { return (va.y.a - va.x.a) * (vb.y.b - vb.x.b) - (va.y.b - va.x.b) * (vb.y.a - vb.x.a) } function is_collinear (p, vec) { var on_x, on_y var pa = round(p.a), pb = round(p.b) if (vec.x.a < vec.y.a) { on_x = vec.x.a <= pa && pa <= vec.y.a } else { on_x = vec.x.a >= pa && pa >= vec.y.a } if (vec.x.b < vec.y.b) { on_y = vec.x.b <= pb && pb <= vec.y.b } else { on_y = vec.x.b >= pb && pb >= vec.y.b } return !! (on_x && on_y) } cursor_copy.extend_ends = function(n){ var a = angle(this) this.x.a -= n*cos(a) this.x.b -= n*sin(a) this.y.a += n*cos(a) this.y.b += n*sin(a) return this } wall_vec.normalize = function(){ var carry if (this.x.a > this.y.a) { // console.log("SWAP X") carry = this.x.a this.x.a = this.y.a this.y.a = carry } if (this.x.b > this.y.b) { // console.log("SWAP Y") carry = this.x.b this.x.b = this.y.b this.y.b = carry } } }