diff --git a/src/graphics.zig b/src/graphics.zig index b1b9602..ca9d93b 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -372,7 +372,7 @@ pub fn beginDraw() bool { // Window is probably hidden if (Graphics.render_target == null) return false; - Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer, &.{ + Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{ .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .cycle = false, .load_op = sdl.GPU_LOADOP_CLEAR, @@ -396,11 +396,35 @@ pub fn beginDraw() bool { return true; } -pub fn drawMesh(mesh: Mesh, texture: Assets.Texture, matrix: Transform.TMatrix) void { +pub fn clearDepth() void { + sdl.EndGPURenderPass(Graphics.render_pass.?); + + Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{ + .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, + .cycle = false, + .load_op = sdl.GPU_LOADOP_LOAD, + .store_op = sdl.GPU_STOREOP_STORE, + .mip_level = 0, + .texture = Graphics.fsaa_target, + }, 1, &.{ + .clear_depth = 1.0, + .load_op = sdl.GPU_LOADOP_CLEAR, + .store_op = sdl.GPU_STOREOP_DONT_CARE, + .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, + .stencil_store_op = sdl.GPU_STOREOP_DONT_CARE, + .texture = Graphics.depth_texture, + }) orelse err.sdl(); + + sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline); + sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1); + sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4); +} + +pub fn drawMesh(mesh: Mesh, texture: Assets.Texture, transform: Transform) void { if (Graphics.render_pass == null) return; const asset_texture = Assets.get(texture) orelse return; - sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &matrix, 16 * 4); + sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4); sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{ .texture = asset_texture.texture, .sampler = asset_texture.sampler, @@ -496,13 +520,15 @@ pub fn getHeight() u32 { return @max(1, Graphics.window_size[1]); } -pub fn generatePlane(x0: f32, y0: f32, x1: f32, y1: f32) [30]f32 { +pub fn generatePlane(x0: f32, y0: f32, x1: f32, y1: f32, w: f32, h: f32) [30]f32 { + const hw = w * 0.5; + const hh = h * 0.5; return .{ - -0.5, -0.5, 0, x0, y1, - 0.5, 0.5, 0, x1, y0, - -0.5, 0.5, 0, x0, y0, - 0.5, 0.5, 0, x1, y0, - -0.5, -0.5, 0, x0, y1, - 0.5, -0.5, 0, x1, y1, + -hw, -hh, 0, x0, y1, + hw, hh, 0, x1, y0, + -hw, hh, 0, x0, y0, + hw, hh, 0, x1, y0, + -hw, -hh, 0, x0, y1, + hw, -hh, 0, x1, y1, }; } diff --git a/src/graphics/camera.zig b/src/graphics/camera.zig index 4b37b4b..8790923 100644 --- a/src/graphics/camera.zig +++ b/src/graphics/camera.zig @@ -52,16 +52,18 @@ pub fn to_screen(camera: Camera, position: Transform.Position) @Vector(2, f32) { return .{ x * wmod, y * wmod }; } -pub fn mouse_in_quad(camera: Camera, mouse: @Vector(2, f32), quad_transform: Transform) bool { +pub fn mouse_in_quad(camera: Camera, mouse: @Vector(2, f32), quad_transform: Transform, width: f32, height: f32) bool { @setFloatMode(.optimized); const matrix = Transform.multiplyMatrix(camera.matrix, quad_transform.matrix()); + const hw = width * 0.5; + const hh = height * 0.5; const pi: [4]@Vector(2, f32) = .{ - .{ -0.5, -0.5 }, - .{ -0.5, 0.5 }, - .{ 0.5, 0.5 }, - .{ 0.5, -0.5 }, + .{ -hw, -hh }, + .{ -hw, hh }, + .{ hw, hh }, + .{ hw, -hh }, }; var po: [4]@Vector(2, f32) = undefined; for (0..4) |i| { diff --git a/src/graphics/transform.zig b/src/graphics/transform.zig index 0a86198..1170824 100644 --- a/src/graphics/transform.zig +++ b/src/graphics/transform.zig @@ -1,51 +1,39 @@ const std = @import("std"); +const math = @import("../math.zig"); const Transform = @This(); pub const TMatrix = @Vector(16, f32); pub const Position = @Vector(3, f32); pub const Rotation = @Vector(4, f32); -pub const Scale = @Vector(3, f32); +pub const Scale = f32; position: Position = @splat(0.0), rotation: Rotation = .{ 1.0, 0.0, 0.0, 0.0 }, -scale: Scale = @splat(1.0), +scale: Scale = 1.0, pub fn matrix(transform: Transform) TMatrix { @setFloatMode(.optimized); - const r = rotationMatrix(transform.rotation); - const sx, const sy, const sz = transform.scale; + const r = rotationMatrix(transform.rotation) * @as(@Vector(9, f32), @splat(transform.scale)); return .{ - sx * r[0], sy * r[1], sz * r[2], transform.position[0], - sx * r[3], sy * r[4], sz * r[5], transform.position[1], - sx * r[6], sy * r[7], sz * r[8], transform.position[2], - 0.0, 0.0, 0.0, 1.0, + r[0], r[1], r[2], transform.position[0], + r[3], r[4], r[5], transform.position[1], + r[6], r[7], r[8], transform.position[2], + 0.0, 0.0, 0.0, 1.0, }; } pub fn inverseMatrix(transform: Transform) TMatrix { @setFloatMode(.optimized); - const r = rotationMatrix(flipRotation(transform.rotation)); + const r = rotationMatrix(flipRotation(transform.rotation)) * @as(@Vector(9, f32), @splat(1.0 / transform.scale)); const tx, const ty, const tz = transform.position; - const sx = 1.0 / transform.scale[0]; - const sy = 1.0 / transform.scale[1]; - const sz = 1.0 / transform.scale[2]; - const r0 = r[0] * sx; - const r1 = r[1] * sx; - const r2 = r[2] * sx; - const r3 = r[3] * sy; - const r4 = r[4] * sy; - const r5 = r[5] * sy; - const r6 = r[6] * sz; - const r7 = r[7] * sz; - const r8 = r[8] * sz; return .{ - r0, r1, r2, -(r0 * tx + r1 * ty + r2 * tz), - r3, r4, r5, -(r3 * tx + r4 * ty + r5 * tz), - r6, r7, r8, -(r6 * tx + r7 * ty + r8 * tz), - 0.0, 0.0, 0.0, 1.0, + r[0], r[1], r[2], -(r[0] * tx + r[1] * ty + r[2] * tz), + r[3], r[4], r[5], -(r[3] * tx + r[4] * ty + r[5] * tz), + r[6], r[7], r[8], -(r[6] * tx + r[7] * ty + r[8] * tz), + 0.0, 0.0, 0.0, 1.0, }; } @@ -94,6 +82,7 @@ pub fn rotateVector(vector: Position, rotation: Rotation) Position { pub fn rotate(transform: *Transform, rotation: Rotation) void { @setFloatMode(.optimized); + // Also rotate `Position` around the origin? transform.rotation = normalizeRotation(combineRotations(transform.rotation, rotation)); } @@ -148,6 +137,8 @@ pub fn normalizeRotation(r: Rotation) Rotation { } } +/// a: Child `Rotation` +/// b: Parent `Rotation` pub fn combineRotations(a: Rotation, b: Rotation) Rotation { @setFloatMode(.optimized); @@ -230,3 +221,31 @@ pub fn multiplyMatrix(a: TMatrix, b: TMatrix) TMatrix { } return output; } + +/// a: Child `Transform` +/// b: Parent `Transform` +pub fn combineTransforms(a: Transform, b: Transform) Transform { + @setFloatMode(.optimized); + + return Transform{ + .position = rotateVector(a.position, b.rotation) * @as(Position, @splat(b.scale)) + b.position, + .rotation = combineRotations(a.rotation, b.rotation), + .scale = a.scale * b.scale, + }; +} + +pub fn lerpTransformTimeLn(a: Transform, b: Transform, t: f32, lnf: f32) Transform { + return lerpTransform(b, a, @exp(lnf * t)); +} + +pub fn lerpTransform(a: Transform, b: Transform, f: f32) Transform { + @setFloatMode(.optimized); + + return Transform{ + .position = math.lerp(a.position, b.position, f), + .rotation = math.slerp(a.rotation, b.rotation, f), + .scale = math.lerp(a.scale, b.scale, f), + }; +} + +pub const ZERO = Transform{}; diff --git a/src/math.zig b/src/math.zig index 731f22a..e64a9c5 100644 --- a/src/math.zig +++ b/src/math.zig @@ -36,15 +36,21 @@ pub inline fn slerpTime(a: anytype, b: anytype, t: f32, comptime f: f32) @TypeOf pub fn slerpTimeLn(a: anytype, b: anytype, t: f32, lnf: f32) @TypeOf(a, b) { @setFloatMode(.optimized); + return slerp(b, a, @exp(lnf * t)); +} + +pub fn slerp(a: anytype, b: anytype, f: f32) @TypeOf(a, b) { + @setFloatMode(.optimized); + const cos = @reduce(.Add, a * b); if (cos > 0.999) { - return lerpTimeLn(a, b, t, lnf); + return lerp(a, b, f); } const angle = std.math.acos(cos); - const a_angle_factor = @exp(lnf * t); - const b_angle_factor = 1.0 - a_angle_factor; + const a_angle_factor = 1 - f; + const b_angle_factor = f; const rev_angle_sin = 1.0 / std.math.sin(angle); const a_sin = std.math.sin(a_angle_factor * angle); @@ -165,3 +171,14 @@ pub fn limit(vector: anytype, value: f32) @TypeOf(vector) { else return vector; } + +pub fn norm(vector: anytype) @TypeOf(vector) { + const len = length(vector); + if (len < 1.01 and len > 0.99) return vector; + if (len < 1e-10) { + var output = @as(@TypeOf(vector), @splat(0)); + output[@typeInfo(@TypeOf(vector)).vector.len - 1] = 1; + return output; + } + return vector * @as(@TypeOf(vector), @splat(1.0 / len)); +} diff --git a/src/world.zig b/src/world.zig index 6a79088..fd22d53 100644 --- a/src/world.zig +++ b/src/world.zig @@ -11,40 +11,69 @@ const Order = i32; pub var object_map: std.AutoHashMapUnmanaged(Id, usize) = .{}; pub var objects: std.ArrayListUnmanaged(Object) = .{}; + pub var plane_mesh: Graphics.Mesh = undefined; pub var cube_mesh: Graphics.Mesh = undefined; pub var table_mesh: Graphics.Mesh = undefined; pub var texture: Assets.Texture = undefined; pub var hand_texture: Assets.Texture = undefined; + pub var camera_position: @Vector(2, f32) = @splat(0); -pub var hover: ?Id = null; pub var hand_transform: Graphics.Transform = .{}; -pub var panning = false; +pub var dock_transform: Graphics.Transform = .{}; pub var zoom: i32 = 0; + +pub var hover: ?Id = null; +pub var panning = false; pub var hand_objects: u32 = 0; +pub var hand_scale: f32 = 0; +pub var dock_objects: u32 = 0; +pub var dock_last_width: f32 = 0; +pub var dock_focused: bool = false; +pub var dock_spacing: f32 = 0; pub var min_order: Order = undefined; pub var max_order: Order = undefined; const Object = struct { transform: Graphics.Transform = .{}, target_transform: Graphics.Transform = .{}, - scale: Graphics.Transform.Scale, + width: f32, + height: f32, mesh: Graphics.Mesh, texture: Assets.Texture, order: Order, id: Id, index: u32, - parent: enum { - none, - hand, - } = .none, - hand_index: u32 = 0, + parent: Parent = .none, + parent_index: u32 = 0, influence: f32 = 0, + const Parent = enum { + none, + hand, + dock, + }; + + pub fn reparent(self: *@This(), new_parent: Parent) void { + self.transform = self.drawingTransform(); + self.influence = 0; + self.parent = new_parent; + } pub fn drawingTransform(self: @This()) Graphics.Transform { - var transform = self.transform; - transform.position += @as(@Vector(3, f32), @splat(self.influence)) * World.hand_transform.position; - return transform; + const transform = self.transform; + const parent_transform = switch (self.parent) { + .hand => World.hand_transform, + .dock => World.dock_transform, + else => return transform, + }; + return Graphics.Transform.combineTransforms( + transform, + Graphics.Transform.lerpTransform( + .{}, + parent_transform, + self.influence, + ), + ); } }; @@ -52,13 +81,9 @@ const World = @This(); pub fn initDebug() void { for (0..10) |i| { (World.objects.addOne(Game.alloc) catch err.oom()).* = .{ - .scale = @splat(0.5), - .mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane( - 15.0 / 16.0, - @as(f32, @floatFromInt(i)) / 16.0, - 16.0 / 16.0, - @as(f32, @floatFromInt(i + 1)) / 16.0, - ))), + .width = 0.5, + .height = 0.5, + .mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane(15.0 / 16.0, @as(f32, @floatFromInt(i)) / 16.0, 16.0 / 16.0, @as(f32, @floatFromInt(i + 1)) / 16.0, 0.5, 0.5))), .texture = Assets.load(.texture, "data/yakuza.png"), .order = @intCast(i), .id = @intCast(i), @@ -66,18 +91,24 @@ pub fn initDebug() void { }; World.object_map.put(Game.alloc, @intCast(i), i) catch err.oom(); } + World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)); World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)); - World.table_mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane(0, 0, 0.5, 0.5))); + World.table_mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane(0, 0, 0.5, 0.5, 8, 8))); World.texture = Assets.load(.texture, "data/yakuza.png"); World.hand_texture = Assets.load(.texture, "data/hand.png"); + World.camera_position = @splat(0); - World.hover = null; - World.hand_transform = .{ - .scale = @splat(0.5), + World.hand_transform = .{}; + World.hand_scale = 0.5; + World.dock_transform = .{ + .position = .{ 0, 0, 4 }, }; - World.panning = false; + World.dock_spacing = 0.2; World.zoom = 0; + + World.panning = false; + World.dock_focused = false; World.min_order = 0; World.max_order = 9; } @@ -97,16 +128,34 @@ pub fn deinit() void { } pub fn update(delta: f32) void { + World.updateCamera(delta); + { + World.dock_transform = Graphics.Transform.lerpTransformTimeLn( + World.dock_transform, + Graphics.Transform.combineTransforms(.{ .position = .{ + 0, + -1, + -1 / Graphics.camera.lens, + } }, Graphics.camera.transform), + delta, + -128, + ); + } + { + const hand_target = Graphics.camera.raycast(.{ Game.mouse.x_norm, Game.mouse.y_norm }, .{ 0, 0, 1, 0 }); + World.hand_transform.position = math.lerpTimeLn( + World.hand_transform.position, + hand_target + @Vector(3, f32){ 0, 0, 0.2 }, + delta, + -24, + ); + } + World.updateOrder(); - const hand_target = Graphics.camera.raycast(.{ Game.mouse.x_norm, Game.mouse.y_norm }, .{ 0, 0, 1, 0 }); - World.hand_transform.position = math.lerpTimeLn( - World.hand_transform.position, - hand_target + @Vector(3, f32){ World.hand_transform.scale[0] * 0.5, -World.hand_transform.scale[1] * 0.5, 0.2 }, - delta, - -24, - ); + World.hover = null; World.hand_objects = 0; + World.dock_objects = 0; for (World.objects.items) |*object| { updateHover(object); } @@ -114,7 +163,6 @@ pub fn update(delta: f32) void { updateObject(object, delta); } World.updateControls(); - World.updateCamera(delta); } pub fn updateControls() void { @@ -151,46 +199,68 @@ pub fn updateControls() void { } World.zoom = std.math.clamp(World.zoom + Game.mouse.wheel, -4, 8); } + if (Game.mouse.y_norm <= -0.8) { + World.dock_focused = true; + } + if (Game.mouse.y_norm >= -0.6) { + World.dock_focused = false; + } } pub fn tryPick() bool { const hover_id = World.hover orelse return false; const object = World.getObject(hover_id) orelse return false; World.panning = false; - object.parent = .hand; + object.reparent(.hand); World.bringToTop(object); return true; } pub fn tryRelease() bool { - var last: ?*Object = null; - for (World.objects.items) |*object| { - if (object.parent != .hand) continue; - last = object; - } - if (last) |object| { - object.transform = object.drawingTransform(); - object.target_transform.position = World.hand_transform.position + @Vector(3, f32){ - -World.hand_transform.scale[0] * 0.5, - World.hand_transform.scale[1] * 0.5, - 0, - }; - object.influence = 0; - object.parent = .none; - return true; - } - return false; + const object = blk: { + var i = World.objects.items.len - 1; + while (true) { + const object = &World.objects.items[i]; + if (object.parent == .hand) { + break :blk object; + } + if (i > 0) + i -= 1 + else + return false; + } + }; + object.target_transform.position = World.hand_transform.position; + World.bringToTop(object); + if (World.dock_focused) + object.reparent(.dock) + else + object.reparent(.none); + return true; } pub fn updateHover(object: *Object) void { - if (object.parent == .hand) { - object.hand_index = World.hand_objects; - World.hand_objects += 1; - return; - } - if (Graphics.camera.mouse_in_quad(.{ Game.mouse.x_norm, Game.mouse.y_norm }, object.target_transform)) { - if (World.hover == null or World.getObject(World.hover.?).?.index < object.index) { - World.hover = object.id; - } + switch (object.parent) { + .none => { + if (!World.dock_focused and Graphics.camera.mouse_in_quad(.{ Game.mouse.x_norm, Game.mouse.y_norm }, object.transform, object.width, object.height)) { + if (World.hover == null or World.getObject(World.hover.?).?.index < object.index) { + World.hover = object.id; + } + } + }, + .hand => { + object.parent_index = World.hand_objects; + World.hand_objects += 1; + }, + .dock => { + object.parent_index = World.dock_objects; + World.dock_last_width = object.width * object.target_transform.scale; + World.dock_objects += 1; + if (World.dock_focused and Graphics.camera.mouse_in_quad(.{ Game.mouse.x_norm, Game.mouse.y_norm }, object.transform.combineTransforms(World.dock_transform), object.width, object.height)) { + if (World.hover == null or World.getObject(World.hover.?).?.index < object.index) { + World.hover = object.id; + } + } + }, } } @@ -198,57 +268,85 @@ pub fn updateObject(object: *Object, delta: f32) void { switch (object.parent) { .none => { object.target_transform.position[2] = if (World.hover == object.id) @as(f32, 0.1) else @as(f32, 0.001) * @as(f32, @floatFromInt(object.index + 1)); - object.target_transform.scale = object.scale; + object.target_transform.scale = 1.0; }, .hand => { var target_position = @as(@Vector(3, f32), @splat(0)); - var target_scale = object.scale; + var target_scale: f32 = 1.0; target_position[2] -= 0.001; - const hand_order = hand_objects - object.hand_index - 1; + const hand_order = hand_objects - object.parent_index - 1; switch (hand_order) { - 0 => { - target_position[0] -= World.hand_transform.scale[0] * 0.5; - target_position[1] += World.hand_transform.scale[1] * 0.5; - }, + 0 => {}, else => |i| { - target_position[0] += World.hand_transform.scale[0] * if (i & 2 == 0) @as(f32, 0.5) else @as(f32, 1); - target_position[1] += World.hand_transform.scale[1] * if ((i - 1) & 2 == 0) @as(f32, 0.25) else @as(f32, -0.25); + target_position[0] += World.hand_scale * if (i & 2 == 0) @as(f32, 1) else @as(f32, 1.5); + target_position[1] += World.hand_scale * if ((i - 1) & 2 == 0) @as(f32, -0.25) else @as(f32, -0.75); target_position[2] -= @as(f32, @floatFromInt((hand_order - 1) / 4)) * 0.001; - target_scale = math.limit(target_scale, World.hand_transform.scale[1] * 0.5); + target_scale = 0.5; }, } object.target_transform.position = target_position; object.target_transform.scale = target_scale; }, + .dock => { + var topleft_x = -World.dock_last_width * 0.5 + World.dock_spacing * (@as(f32, @floatFromInt(object.parent_index)) - @as(f32, @floatFromInt(World.dock_objects - 1)) * 0.5); + const total_w = @as(f32, @floatFromInt(World.dock_objects - 1)) * World.dock_spacing + World.dock_last_width; + if (total_w > Graphics.camera.aspect * 2) { + topleft_x += math.lerp(0, Graphics.camera.aspect - total_w * 0.5, Game.mouse.x_norm); + } + const hit = World.hover == object.id; + const topleft_y = if (World.dock_focused) if (hit) @as(f32, 0.5) else @as(f32, 0.3) else @as(f32, 0.2); + object.target_transform.position = .{ + topleft_x + object.width * 0.5 * object.target_transform.scale, + topleft_y - object.height * 0.5 * object.target_transform.scale, + if (hit) @as(f32, 0.02) else @as(f32, 0), + }; + object.target_transform.rotation = if (hit) + Graphics.Transform.ZERO.rotation + else + Graphics.Transform.rotationByAxis(.{ 0, 1, 0 }, 0.001); + }, } - if (object.parent == .hand) { + if (object.parent != .none) { object.influence = math.lerpTimeLn( object.influence, 1.0, delta, - -8, + -24, ); } - object.transform.position = math.lerpTimeLn( - object.transform.position, - object.target_transform.position, + object.transform = Graphics.Transform.lerpTransformTimeLn( + object.transform, + object.target_transform, delta, - -8, - ); - object.transform.scale = math.lerpTimeLn( - object.transform.scale, - object.target_transform.scale, - delta, - -8, + -24, ); } pub fn draw() void { - Graphics.drawMesh(World.table_mesh, World.texture, Graphics.Transform.matrix(.{ .scale = @splat(8) })); + Graphics.drawMesh(World.table_mesh, World.texture, .{}); + for (World.objects.items) |*object| { - Graphics.drawMesh(object.mesh, object.texture, object.drawingTransform().matrix()); + if (object.parent != .dock) + Graphics.drawMesh(object.mesh, object.texture, object.drawingTransform()); + } + + Graphics.drawMesh( + World.plane_mesh, + World.hand_texture, + Graphics.Transform.combineTransforms( + .{ + .position = .{ World.hand_scale * 0.5, -World.hand_scale * 0.5, 0 }, + .scale = World.hand_scale, + }, + World.hand_transform, + ), + ); + + Graphics.clearDepth(); + for (World.objects.items) |*object| { + if (object.parent == .dock) + Graphics.drawMesh(object.mesh, object.texture, object.drawingTransform()); } - Graphics.drawMesh(World.plane_mesh, World.hand_texture, World.hand_transform.matrix()); } pub fn updateCamera(delta: f32) void { @@ -381,3 +479,11 @@ const PLANE_MESH_DATA = [_]f32{ -0.5, -0.5, 0, 0.0, 1.0, 0.5, -0.5, 0, 1.0, 1.0, }; +const PLANE_MESH_DATA_HALF = [_]f32{ + -0.25, -0.25, 0, 0.0, 1.0, + 0.25, 0.25, 0, 1.0, 0.0, + -0.25, 0.25, 0, 0.0, 0.0, + 0.25, 0.25, 0, 1.0, 0.0, + -0.25, -0.25, 0, 0.0, 1.0, + 0.25, -0.25, 0, 1.0, 1.0, +};