Time travel!
This commit is contained in:
@@ -56,41 +56,59 @@ const PLANE_MESH_DATA = [_]f32{
|
|||||||
|
|
||||||
pub const WorldTime = struct {
|
pub const WorldTime = struct {
|
||||||
time: Time,
|
time: Time,
|
||||||
delta: f32,
|
last_time: Time,
|
||||||
view_unresolved: f32,
|
view_unresolved: f32,
|
||||||
view_timescale: f32,
|
view_timescale: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const PlayerState = union(enum) {
|
||||||
|
idle,
|
||||||
|
moving: struct {
|
||||||
|
from_position: @Vector(3, f32),
|
||||||
|
from_time: Time,
|
||||||
|
to_time: Time,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub const Player = struct {
|
pub const Player = struct {
|
||||||
mesh: Graphics.Mesh,
|
mesh: Graphics.Mesh,
|
||||||
transform: Graphics.Transform,
|
transform: Transform,
|
||||||
velocity: @Vector(2, f32),
|
position: @Vector(2, i32),
|
||||||
|
state: PlayerState,
|
||||||
|
|
||||||
|
move_units: f32,
|
||||||
|
idle_timescale: f32,
|
||||||
|
moving_timescale: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Environment = struct {
|
pub const Environment = struct {
|
||||||
mesh: Graphics.Mesh,
|
mesh: Graphics.Mesh,
|
||||||
|
transform: Transform,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(controller: *Controller, graphics: *Graphics) !void {
|
pub fn init(controller: *Controller, graphics: *Graphics) !void {
|
||||||
controller.addResource(Player{
|
controller.addResource(Player{
|
||||||
.mesh = try graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)),
|
.mesh = try graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)),
|
||||||
.transform = Graphics.Transform{
|
.transform = .{},
|
||||||
.position = .{ 0, 0, 1 },
|
.position = .{ 0, 0 },
|
||||||
},
|
.state = .idle,
|
||||||
.velocity = .{ 0, 0 },
|
.move_units = 0.125,
|
||||||
|
.moving_timescale = 1.0,
|
||||||
|
.idle_timescale = 0.0625,
|
||||||
});
|
});
|
||||||
controller.addResource(Environment{
|
controller.addResource(Environment{
|
||||||
.mesh = try graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)),
|
.mesh = try graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)),
|
||||||
|
.transform = .{
|
||||||
|
.position = .{ 0, 0, -1 },
|
||||||
|
.scale = @splat(5),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
controller.addResource(WorldTime{
|
controller.addResource(WorldTime{
|
||||||
.time = .{ .clock = 0 },
|
.time = Time.ZERO,
|
||||||
.delta = 0.0,
|
.last_time = Time.ZERO,
|
||||||
.view_unresolved = 0.0,
|
.view_unresolved = 0.0,
|
||||||
.view_timescale = 1.0,
|
.view_timescale = 0.0625,
|
||||||
});
|
});
|
||||||
graphics.camera.transform = .{
|
|
||||||
.position = .{ 0, 0, 10 },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit() void {}
|
pub fn deinit() void {}
|
||||||
@@ -101,70 +119,101 @@ pub fn updateReal(
|
|||||||
controller: *Controller,
|
controller: *Controller,
|
||||||
) void {
|
) void {
|
||||||
world_time.view_unresolved += real_time.delta;
|
world_time.view_unresolved += real_time.delta;
|
||||||
controller.queue(updateWorld);
|
controller.queue(.{
|
||||||
|
updateWorld,
|
||||||
|
updatePlayerTransform,
|
||||||
|
updateCamera,
|
||||||
|
Controller.Option.ordered,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateWorld(
|
pub fn updateWorld(
|
||||||
world_time: *WorldTime,
|
world_time: *WorldTime,
|
||||||
controller: *Controller,
|
controller: *Controller,
|
||||||
|
player: *Player,
|
||||||
) void {
|
) void {
|
||||||
if (world_time.view_unresolved <= 0.000001) {
|
if (world_time.view_unresolved <= 0) return;
|
||||||
return;
|
|
||||||
|
var real_delta = world_time.view_unresolved;
|
||||||
|
var world_delta = Time.durationFromUnits(real_delta * world_time.view_timescale);
|
||||||
|
|
||||||
|
switch (player.state) {
|
||||||
|
.moving => |move| {
|
||||||
|
if (move.to_time.clock > world_time.time.clock) {
|
||||||
|
world_delta = @min(world_delta, move.to_time.clock - world_time.time.clock);
|
||||||
|
real_delta = Time.unitsFromDuration(world_delta) / world_time.view_timescale;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.idle => {},
|
||||||
}
|
}
|
||||||
// This can later be clamped to schedule several updates per frame
|
|
||||||
const real_delta = world_time.view_unresolved;
|
|
||||||
const world_delta = real_delta * world_time.view_timescale;
|
|
||||||
|
|
||||||
world_time.time.tick(real_delta);
|
if (world_delta == 0) return;
|
||||||
|
|
||||||
|
world_time.last_time = world_time.time;
|
||||||
|
|
||||||
|
world_time.time.clock += world_delta;
|
||||||
world_time.view_unresolved -= real_delta;
|
world_time.view_unresolved -= real_delta;
|
||||||
world_time.delta = world_delta;
|
|
||||||
|
|
||||||
controller.queue(update);
|
controller.queue(.{
|
||||||
|
updatePlayer,
|
||||||
|
updateWorld,
|
||||||
|
Controller.Option.ordered,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(
|
pub fn updatePlayer(
|
||||||
player: *Player,
|
player: *Player,
|
||||||
// mouse: *Game.Mouse,
|
|
||||||
keyboard: *Game.Keyboard,
|
keyboard: *Game.Keyboard,
|
||||||
graphics: *Graphics,
|
|
||||||
real_time: *Game.Time,
|
|
||||||
world_time: *WorldTime,
|
world_time: *WorldTime,
|
||||||
) void {
|
) void {
|
||||||
const MAX_VELOCITY = 12.0;
|
switch (player.state) {
|
||||||
const TIME_TO_REACH_MAX_VELOCITY = 1.0 / 8.0;
|
.idle => {},
|
||||||
|
.moving => |move| {
|
||||||
|
if (world_time.time.past(move.to_time)) {
|
||||||
|
player.state = .idle;
|
||||||
|
world_time.view_timescale = player.idle_timescale;
|
||||||
|
} else return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var velocity_target: @Vector(2, f32) = .{ 0, 0 };
|
var delta: @Vector(2, i32) = .{ 0, 0 };
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_W)) {
|
if (keyboard.keys.is_pressed(sdl.SCANCODE_W)) {
|
||||||
velocity_target[1] += MAX_VELOCITY;
|
delta[1] += 1;
|
||||||
}
|
}
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_S)) {
|
if (keyboard.keys.is_pressed(sdl.SCANCODE_S)) {
|
||||||
velocity_target[1] -= MAX_VELOCITY;
|
delta[1] -= 1;
|
||||||
}
|
}
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_D)) {
|
if (keyboard.keys.is_pressed(sdl.SCANCODE_D)) {
|
||||||
velocity_target[0] += MAX_VELOCITY;
|
delta[0] += 1;
|
||||||
}
|
}
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_A)) {
|
if (keyboard.keys.is_pressed(sdl.SCANCODE_A)) {
|
||||||
velocity_target[0] -= MAX_VELOCITY;
|
delta[0] -= 1;
|
||||||
}
|
}
|
||||||
velocity_target = math.limitLength(velocity_target, MAX_VELOCITY);
|
if (delta[0] != 0 or delta[1] != 0) {
|
||||||
player.velocity = math.stepVector(player.velocity, velocity_target, MAX_VELOCITY / TIME_TO_REACH_MAX_VELOCITY * world_time.delta);
|
updatePlayerTransform(player, world_time);
|
||||||
player.transform.position[0] += player.velocity[0] * world_time.delta;
|
player.state = .{ .moving = .{
|
||||||
player.transform.position[1] += player.velocity[1] * world_time.delta;
|
.from_position = player.transform.position,
|
||||||
|
.from_time = world_time.time,
|
||||||
const target_position = player.transform.position +
|
.to_time = world_time.time.offset(player.move_units * math.lengthInt(delta)),
|
||||||
@Vector(3, f32){ player.velocity[0], player.velocity[1], 0 } *
|
} };
|
||||||
@as(@Vector(3, f32), @splat(1.0 / MAX_VELOCITY)) *
|
player.position[0] += delta[0];
|
||||||
@Vector(3, f32){ 0.0, 0.0, 0.0 };
|
player.position[1] += delta[1];
|
||||||
|
world_time.view_timescale = player.moving_timescale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateCamera(
|
||||||
|
graphics: *Graphics,
|
||||||
|
player: *Player,
|
||||||
|
real_time: *Game.Time,
|
||||||
|
) void {
|
||||||
graphics.camera.transform.position = math.lerpTimeLn(
|
graphics.camera.transform.position = math.lerpTimeLn(
|
||||||
graphics.camera.transform.position,
|
graphics.camera.transform.position,
|
||||||
target_position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
|
player.transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
|
||||||
real_time.delta,
|
real_time.delta,
|
||||||
-25,
|
-25,
|
||||||
);
|
);
|
||||||
|
|
||||||
{ // Rotate camera toward player
|
|
||||||
|
|
||||||
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
|
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
|
||||||
const INIT_ROTATION = Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
|
const INIT_ROTATION = Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
|
||||||
|
|
||||||
@@ -174,7 +223,7 @@ pub fn update(
|
|||||||
INIT_ROTATION,
|
INIT_ROTATION,
|
||||||
Transform.rotationToward(
|
Transform.rotationToward(
|
||||||
ROTATED_DIR,
|
ROTATED_DIR,
|
||||||
target_position - graphics.camera.transform.position,
|
player.transform.position - graphics.camera.transform.position,
|
||||||
.{ .normalize_to = true },
|
.{ .normalize_to = true },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -184,6 +233,19 @@ pub fn update(
|
|||||||
real_time.delta,
|
real_time.delta,
|
||||||
-2,
|
-2,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updatePlayerTransform(player: *Player, world_time: *WorldTime) void {
|
||||||
|
switch (player.state) {
|
||||||
|
.idle => player.transform.position = .{ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 },
|
||||||
|
.moving => |move| {
|
||||||
|
const to_position = @Vector(3, f32){ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 };
|
||||||
|
player.transform.position = math.lerp(
|
||||||
|
move.from_position,
|
||||||
|
to_position,
|
||||||
|
world_time.time.progress(move.from_time, move.to_time),
|
||||||
|
);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,9 +253,16 @@ pub fn draw(
|
|||||||
player: *Player,
|
player: *Player,
|
||||||
env: *Environment,
|
env: *Environment,
|
||||||
graphics: *Graphics,
|
graphics: *Graphics,
|
||||||
|
world_time: *WorldTime,
|
||||||
) !void {
|
) !void {
|
||||||
try graphics.drawMesh(env.mesh, Graphics.Transform{
|
env.transform.rotation = Transform.combineRotations(
|
||||||
.scale = .{ 10, 10, 10 },
|
env.transform.rotation,
|
||||||
|
Transform.rotationByAxis(.{ 0, 0, 1 }, world_time.time.unitsSince(world_time.last_time) * std.math.pi),
|
||||||
|
);
|
||||||
|
try graphics.drawMesh(env.mesh, env.transform);
|
||||||
|
try graphics.drawMesh(env.mesh, Transform{
|
||||||
|
.position = .{ 0, 0, -0.5 },
|
||||||
|
.scale = @splat(5),
|
||||||
});
|
});
|
||||||
try graphics.drawMesh(player.mesh, player.transform);
|
try graphics.drawMesh(player.mesh, player.transform);
|
||||||
}
|
}
|
||||||
|
@@ -68,9 +68,11 @@ pub fn run(self: *Self) GameError!void {
|
|||||||
var current_time: sdl.Time = undefined;
|
var current_time: sdl.Time = undefined;
|
||||||
if (sdl.GetCurrentTime(¤t_time)) {
|
if (sdl.GetCurrentTime(¤t_time)) {
|
||||||
const time = self.graph.getResource(Time).?;
|
const time = self.graph.getResource(Time).?;
|
||||||
|
if (time.now != 0) {
|
||||||
time.delta = @as(f32, @floatFromInt(current_time - time.now)) * 0.000000001;
|
time.delta = @as(f32, @floatFromInt(current_time - time.now)) * 0.000000001;
|
||||||
time.now = current_time;
|
|
||||||
}
|
}
|
||||||
|
time.now = current_time;
|
||||||
|
} else return error.SdlError;
|
||||||
|
|
||||||
var controller = try self.graph.getController();
|
var controller = try self.graph.getController();
|
||||||
controller.queue(.{
|
controller.queue(.{
|
||||||
|
@@ -15,7 +15,7 @@ pub const Controller = @import("graph/controller.zig");
|
|||||||
const MAX_SYSTEM_REQUESTS = 8;
|
const MAX_SYSTEM_REQUESTS = 8;
|
||||||
const DEFAULT_SYSTEM_CAPACITY = 16;
|
const DEFAULT_SYSTEM_CAPACITY = 16;
|
||||||
const DEFAULT_CONTROLLERS = 2;
|
const DEFAULT_CONTROLLERS = 2;
|
||||||
const DEFAULT_DUDS_PER_CONTROLLER = 4;
|
const DEFAULT_DUDS_PER_CONTROLLER = 32;
|
||||||
|
|
||||||
const ResourceMap = std.AutoArrayHashMapUnmanaged(utils.Hash, Resource);
|
const ResourceMap = std.AutoArrayHashMapUnmanaged(utils.Hash, Resource);
|
||||||
const SystemQueue = std.ArrayListUnmanaged(System);
|
const SystemQueue = std.ArrayListUnmanaged(System);
|
||||||
|
@@ -181,6 +181,9 @@ pub fn extractNormal(vector: Position) struct { Position, f32 } {
|
|||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
const length = @sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
|
const length = @sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
|
||||||
|
if (length < 1e-15) {
|
||||||
|
return .{ @Vector(3, f32){ 0, 0, -1 }, 1 };
|
||||||
|
}
|
||||||
return .{ vector / @as(Position, @splat(length)), length };
|
return .{ vector / @as(Position, @splat(length)), length };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
src/math.zig
22
src/math.zig
@@ -10,8 +10,14 @@ pub inline fn lerpTime(a: anytype, b: anytype, t: f32, comptime f: f32) @TypeOf(
|
|||||||
pub fn lerpTimeLn(a: anytype, b: anytype, t: f32, lnf: f32) @TypeOf(a, b) {
|
pub fn lerpTimeLn(a: anytype, b: anytype, t: f32, lnf: f32) @TypeOf(a, b) {
|
||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
const a_factor = @exp(lnf * t);
|
return lerp(b, a, @exp(lnf * t));
|
||||||
const b_factor = 1.0 - a_factor;
|
}
|
||||||
|
|
||||||
|
pub fn lerp(a: anytype, b: anytype, f: f32) @TypeOf(a, b) {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
const a_factor = 1.0 - f;
|
||||||
|
const b_factor = f;
|
||||||
|
|
||||||
switch (@typeInfo(@TypeOf(a, b))) {
|
switch (@typeInfo(@TypeOf(a, b))) {
|
||||||
.float => return a_factor * a + b_factor * b,
|
.float => return a_factor * a + b_factor * b,
|
||||||
@@ -88,3 +94,15 @@ pub fn dot(a: anytype, b: anytype) f32 {
|
|||||||
|
|
||||||
return @reduce(.Add, a * b);
|
return @reduce(.Add, a * b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lengthInt(vector: anytype) f32 {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
return @sqrt(@as(f32, @floatFromInt(dotInt(vector, vector))));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dotInt(a: anytype, b: anytype) (@typeInfo(@TypeOf(a)).vector.child) {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
return @reduce(.Add, a * b);
|
||||||
|
}
|
||||||
|
27
src/time.zig
27
src/time.zig
@@ -1,20 +1,43 @@
|
|||||||
const TimeType = u64;
|
const TimeType = u64;
|
||||||
const TIME_UNIT: TimeType = 1 << 32;
|
const TIME_UNIT: TimeType = 1 << 32;
|
||||||
const TIME_MULT = 1.0 / @as(f32, @floatFromInt(TIME_UNIT));
|
const TIME_MULT = 1.0 / @as(f32, @floatFromInt(TIME_UNIT));
|
||||||
|
const Time = @This();
|
||||||
|
|
||||||
|
pub const ZERO = Time{ .clock = 0 };
|
||||||
|
|
||||||
clock: TimeType,
|
clock: TimeType,
|
||||||
|
|
||||||
const Time = @This();
|
|
||||||
|
|
||||||
pub fn tick(self: *Time, units: f32) void {
|
pub fn tick(self: *Time, units: f32) void {
|
||||||
self.clock += durationFromUnits(units);
|
self.clock += durationFromUnits(units);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn past(self: *Time, goal: Time) bool {
|
||||||
|
return self.clock >= goal.clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(self: Time, units: f32) Time {
|
||||||
|
return Time{
|
||||||
|
.clock = self.clock + durationFromUnits(units),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unitsSince(self: *Time, from: Time) f32 {
|
pub fn unitsSince(self: *Time, from: Time) f32 {
|
||||||
if (from.clock > self.clock) return 0;
|
if (from.clock > self.clock) return 0;
|
||||||
return @as(f32, @floatFromInt(self.clock - from.clock)) * TIME_MULT;
|
return @as(f32, @floatFromInt(self.clock - from.clock)) * TIME_MULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn progress(self: *Time, from: Time, to: Time) f32 {
|
||||||
|
if (from.clock > to.clock) return 1.0;
|
||||||
|
if (self.clock > to.clock) return 1.0;
|
||||||
|
|
||||||
|
const duration = to.clock - from.clock;
|
||||||
|
return @as(f32, @floatFromInt(self.clock - from.clock)) / @as(f32, @floatFromInt(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unitsFromDuration(duration: TimeType) f32 {
|
||||||
|
return @as(f32, @floatFromInt(duration)) * TIME_MULT;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn durationFromUnits(units: f32) TimeType {
|
pub fn durationFromUnits(units: f32) TimeType {
|
||||||
return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units);
|
return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user