diff --git a/misc/multitouch_cubes/CubeScene.tscn b/misc/multitouch_cubes/CubeScene.tscn new file mode 100644 index 00000000..d3048e3c --- /dev/null +++ b/misc/multitouch_cubes/CubeScene.tscn @@ -0,0 +1,151 @@ +[gd_scene load_steps=3 format=1] + +[sub_resource type="FixedMaterial" id=1] + +flags/visible = true +flags/double_sided = false +flags/invert_faces = false +flags/unshaded = false +flags/on_top = false +flags/lightmap_on_uv2 = true +flags/colarray_is_srgb = true +params/blend_mode = 0 +params/depth_draw = 1 +params/line_width = 0.0 +fixed_flags/use_alpha = false +fixed_flags/use_color_array = false +fixed_flags/use_point_size = false +fixed_flags/discard_alpha = false +fixed_flags/use_xy_normalmap = false +params/diffuse = Color( 0.259476, 0.699219, 0.379718, 1 ) +params/specular = Color( 1, 1, 1, 1 ) +params/emission = Color( 0, 0, 0, 1 ) +params/specular_exp = 40 +params/detail_mix = 1.0 +params/normal_depth = 1 +params/shader = 0 +params/shader_param = 0.5 +params/glow = 0 +params/point_size = 1.0 +uv_xform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) +textures/diffuse_tc = 0 +textures/detail_tc = 0 +textures/specular_tc = 0 +textures/emission_tc = 0 +textures/specular_exp_tc = 0 +textures/glow_tc = 0 +textures/normal_tc = 0 +textures/shade_param_tc = 0 + +[sub_resource type="Environment" id=2] + +ambient_light/enabled = true +ambient_light/color = Color( 0.601563, 0.696429, 1, 1 ) +ambient_light/energy = 0.5 +fxaa/enabled = true +background/mode = 1 +background/color = Color( 0, 0, 0, 1 ) +background/energy = 1.0 +background/scale = 1.0 +background/glow = 0.0 +background/canvas_max_layer = null +glow/enabled = false +glow/blur_passes = 1 +glow/blur_scale = 1 +glow/blur_strength = 1 +glow/blur_blend_mode = null +glow/bloom = 0.0 +glow/bloom_treshold = 0.5 +dof_blur/enabled = false +dof_blur/blur_passes = 1 +dof_blur/begin = 100.0 +dof_blur/range = 10.0 +hdr/enabled = false +hdr/tonemapper = 0 +hdr/exposure = 0.4 +hdr/white = 1.0 +hdr/glow_treshold = 0.95 +hdr/glow_scale = 0.2 +hdr/min_luminance = 0.4 +hdr/max_luminance = 8.0 +hdr/exposure_adj_speed = 0.5 +fog/enabled = false +fog/begin = 100.0 +fog/begin_color = Color( 0, 0, 0, 1 ) +fog/end_color = Color( 0, 0, 0, 1 ) +fog/attenuation = 1.0 +fog/bg = true +bcs/enabled = false +bcs/brightness = 1.0 +bcs/contrast = 1.0 +bcs/saturation = 1.0 +srgb/enabled = false + +[node name="Spatial" type="Spatial"] + +_import_transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) + +[node name="TestCube" type="TestCube" parent="."] + +_import_transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) +transform/local = Transform( 0.707107, 0, -0.707107, -0.353553, 0.866025, -0.353553, 0.612372, 0.5, 0.612372, 0, 0, 0 ) +layers = 1 +geometry/visible = true +geometry/material_override = SubResource( 1 ) +geometry/cast_shadow = 1 +geometry/receive_shadows = true +geometry/range_begin = 0.0 +geometry/range_end = 0.0 +geometry/extra_cull_margin = 0.0 +geometry/billboard = false +geometry/billboard_y = false +geometry/depth_scale = false +geometry/visible_in_all_rooms = false +geometry/use_baked_light = false +geometry/baked_light_tex_id = 0 + +[node name="Camera" type="Camera" parent="."] + +_import_transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) +transform/local = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7.09558 ) +projection = 0 +fov = 50.0 +near = 0.1 +far = 100.0 +keep_aspect = 1 +current = false +visible_layers = 1048575 +environment = null +h_offset = 0.0 +v_offset = 0.0 + +[node name="DirectionalLight" type="DirectionalLight" parent="."] + +_import_transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) +transform/local = Transform( 0.926535, 0.11439, -0.358396, 0.199614, 0.658013, 0.726067, 0.318884, -0.744267, 0.586839, 0, 4.14991, 0 ) +layers = 1 +params/enabled = true +params/editor_only = false +params/bake_mode = 0 +params/energy = 1.0 +colors/diffuse = Color( 1, 1, 1, 1 ) +colors/specular = Color( 1, 1, 1, 1 ) +shadow/shadow = false +shadow/darkening = 0.0 +shadow/z_offset = 0.05 +shadow/z_slope_scale = 0.0 +shadow/esm_multiplier = 60.0 +shadow/blur_passes = 1.0 +projector = null +operator = 0 +shadow/mode = 0 +shadow/max_distance = 0.0 +shadow/split_weight = 0.5 +shadow/zoffset_scale = 2.0 + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] + +_import_transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) +environment = SubResource( 2 ) + + diff --git a/misc/multitouch_cubes/GestureArea.gd b/misc/multitouch_cubes/GestureArea.gd new file mode 100644 index 00000000..2831d309 --- /dev/null +++ b/misc/multitouch_cubes/GestureArea.gd @@ -0,0 +1,133 @@ +extends Control + +export(NodePath) var target +export var min_scale = 0.1 +export var max_scale = 3.0 +export var one_finger_rot_x = true +export var one_finger_rot_y = true +export var two_fingers_rot_z = true +export var two_fingers_zoom = true + +var base_state +var curr_state + +var target_node + +# We keep here a copy of the state before the number of fingers changed to avoid accumulation errors +var base_xform + +func _ready(): + base_state = {} + curr_state = {} + target_node = get_node(target) + set_process_unhandled_input(true) + +# Using unhandled input since it's the method that has empirically proven to work the best +func _unhandled_input(event): + # We must start touching inside, but we can drag or unpress outside + if !(event.type == InputEvent.SCREEN_DRAG || + (event.type == InputEvent.SCREEN_TOUCH && (!event.pressed || get_global_rect().has_point(Vector2(event.x, event.y))))): + return + + var finger_count_changed = false + var finger_count = base_state.size() + + if finger_count == 0: + # No fingers => Accept press + + if event.type == InputEvent.SCREEN_TOUCH: + if event.pressed: + # A finger started touching + + base_state = { + event.index: Vector2(event.x, event.y), + } + + elif finger_count == 1: + # One finger => For rotating around X and Y + # Accept one more press, unpress or drag + + if event.type == InputEvent.SCREEN_TOUCH: + if event.pressed: + # One more finger started touching + + # Reset the base state to the only current and the new fingers + base_state = { + curr_state.keys()[0]: curr_state.values()[0], + event.index: Vector2(event.x, event.y), + } + else: + if base_state.has(event.index): + # Only touching finger released + + base_state.clear() + + elif event.type == InputEvent.SCREEN_DRAG: + if curr_state.has(event.index): + # Touching finger dragged + + var unit_drag = _px2unit(base_state[base_state.keys()[0]] - Vector2(event.x, event.y)) + if one_finger_rot_x: + target_node.global_rotate(Vector3(0, 1, 0), deg2rad(180.0 * unit_drag.x)) + if one_finger_rot_y: + target_node.global_rotate(Vector3(1, 0, 0), deg2rad(180.0 * unit_drag.y)) + # Since rotating around two axes, we have to reset the base constantly + curr_state[event.index] = Vector2(event.x, event.y) + base_state[event.index] = Vector2(event.x, event.y) + base_xform = target_node.get_transform() + + elif finger_count == 2: + # Two fingers => To pinch-zoom and rotate around Z + # Accept unpress or drag + + if event.type == InputEvent.SCREEN_TOUCH: + if !event.pressed && base_state.has(event.index): + # Some known touching finger released + + # Remove released finger from the base state + base_state.erase(event.index) + # Reset the base state to the now only toyching finger + base_state = { + curr_state.keys()[0]: curr_state.values()[0], + } + + elif event.type == InputEvent.SCREEN_DRAG: + if curr_state.has(event.index): + # Some known touching finger dragged + + # Update + curr_state[event.index] = Vector2(event.x, event.y) + + # Compute base and current inter-finger vectors + var base_segment = base_state[base_state.keys()[0]] - base_state[base_state.keys()[1]] + var new_segment = curr_state[curr_state.keys()[0]] - curr_state[curr_state.keys()[1]] + + # Get the base scale from the base matrix + var base_scale = Vector3(base_xform.basis.x.x, base_xform.basis.y.y, base_xform.basis.z.z).length() + + if two_fingers_zoom: + # Compute the new scale limiting it and taking into account the base scale + var new_scale = clamp(base_scale * (new_segment.length() / base_segment.length()), min_scale, max_scale) / base_scale + target_node.set_transform(base_xform.scaled(new_scale * Vector3(1, 1, 1))) + else: + target_node.set_transform(base_xform) + + if two_fingers_rot_z: + # Apply rotation between base inter-finger vector and the current one + var rot = new_segment.angle_to(base_segment) + target_node.global_rotate(Vector3(0, 0, 1), rot) + + # Finger count changed? + if base_state.size() != finger_count: + # Copy new base state to the current state + curr_state = {} + for idx in base_state.keys(): + curr_state[idx] = base_state[idx] + # Remember the base transform + base_xform = target_node.get_transform() + +# Converts a vector in pixels to a unitary magnitude, +# considering the number of pixels of the shorter axis is the unit +func _px2unit(v): + var shortest = min(get_size().x, get_size().y) + return v * (1.0 / shortest) diff --git a/misc/multitouch_cubes/Main.tscn b/misc/multitouch_cubes/Main.tscn new file mode 100644 index 00000000..25bb9169 --- /dev/null +++ b/misc/multitouch_cubes/Main.tscn @@ -0,0 +1,256 @@ +[gd_scene load_steps=3 format=1] + +[ext_resource path="res://GestureArea.gd" type="Script" id=1] +[ext_resource path="res://CubeScene.tscn" type="PackedScene" id=2] + +[node name="VBoxContainer" type="VBoxContainer"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +alignment = 0 + +[node name="HBoxContainer" type="HBoxContainer" parent="."] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 512.0 +margin/bottom = 254.0 +alignment = 0 + +[node name="GestureArea" type="Control" parent="HBoxContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 254.0 +margin/bottom = 254.0 +script/script = ExtResource( 1 ) +target = NodePath("Viewport/Spatial/TestCube") +min_scale = 0.1 +max_scale = 3.0 +one_finger_rot_x = true +one_finger_rot_y = false +two_fingers_rot_z = false +two_fingers_zoom = false + +[node name="Viewport" type="Viewport" parent="HBoxContainer/GestureArea"] + +rect = Rect2( 0, 0, 254, 254 ) +own_world = true +world = null +transparent_bg = false +render_target/enabled = false +render_target/v_flip = false +render_target/clear_on_new_frame = true +render_target/filter = false +render_target/gen_mipmaps = false +render_target/update_mode = 2 +audio_listener/enable_2d = false +audio_listener/enable_3d = false +physics/object_picking = false +gui/disable_input = false + +[node name="Spatial" parent="HBoxContainer/GestureArea/Viewport" instance=ExtResource( 2 )] + +[node name="Label" type="Label" parent="HBoxContainer/GestureArea"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 7.0 +margin/top = 6.0 +margin/right = 157.0 +margin/bottom = 20.0 +text = "One-finger rot around X" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="GestureArea_1" type="Control" parent="HBoxContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 258.0 +margin/top = 0.0 +margin/right = 512.0 +margin/bottom = 254.0 +script/script = ExtResource( 1 ) +target = NodePath("Viewport/Spatial/TestCube") +min_scale = 0.1 +max_scale = 3.0 +one_finger_rot_x = true +one_finger_rot_y = true +two_fingers_rot_z = false +two_fingers_zoom = false + +[node name="Viewport" type="Viewport" parent="HBoxContainer/GestureArea_1"] + +rect = Rect2( 0, 0, 254, 254 ) +own_world = true +world = null +transparent_bg = false +render_target/enabled = false +render_target/v_flip = false +render_target/clear_on_new_frame = true +render_target/filter = false +render_target/gen_mipmaps = false +render_target/update_mode = 2 +audio_listener/enable_2d = false +audio_listener/enable_3d = false +physics/object_picking = false +gui/disable_input = false + +[node name="Spatial" parent="HBoxContainer/GestureArea_1/Viewport" instance=ExtResource( 2 )] + +[node name="Label2" type="Label" parent="HBoxContainer/GestureArea_1"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 15.0 +margin/top = 6.0 +margin/right = 55.0 +margin/bottom = 20.0 +text = "One-finger rot around X and Y" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="HBoxContainer_1" type="HBoxContainer" parent="."] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 258.0 +margin/right = 512.0 +margin/bottom = 512.0 +alignment = 0 + +[node name="GestureArea" type="Control" parent="HBoxContainer_1"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 254.0 +margin/bottom = 254.0 +script/script = ExtResource( 1 ) +target = NodePath("Viewport/Spatial/TestCube") +min_scale = 0.1 +max_scale = 3.0 +one_finger_rot_x = true +one_finger_rot_y = true +two_fingers_rot_z = true +two_fingers_zoom = false + +[node name="Viewport" type="Viewport" parent="HBoxContainer_1/GestureArea"] + +rect = Rect2( 0, 0, 254, 254 ) +own_world = true +world = null +transparent_bg = false +render_target/enabled = false +render_target/v_flip = false +render_target/clear_on_new_frame = true +render_target/filter = false +render_target/gen_mipmaps = false +render_target/update_mode = 2 +audio_listener/enable_2d = false +audio_listener/enable_3d = false +physics/object_picking = false +gui/disable_input = false + +[node name="Spatial" parent="HBoxContainer_1/GestureArea/Viewport" instance=ExtResource( 2 )] + +[node name="Label2" type="Label" parent="HBoxContainer_1/GestureArea"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 7.0 +margin/top = 12.0 +margin/right = 47.0 +margin/bottom = 26.0 +text = "One-finger X/Y rot + two-finger Z rot" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="GestureArea_1" type="Control" parent="HBoxContainer_1"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 258.0 +margin/top = 0.0 +margin/right = 512.0 +margin/bottom = 254.0 +script/script = ExtResource( 1 ) +target = NodePath("Viewport/Spatial/TestCube") +min_scale = 0.1 +max_scale = 3.0 +one_finger_rot_x = true +one_finger_rot_y = true +two_fingers_rot_z = true +two_fingers_zoom = true + +[node name="Viewport" type="Viewport" parent="HBoxContainer_1/GestureArea_1"] + +rect = Rect2( 0, 0, 254, 254 ) +own_world = true +world = null +transparent_bg = false +render_target/enabled = false +render_target/v_flip = false +render_target/clear_on_new_frame = true +render_target/filter = false +render_target/gen_mipmaps = false +render_target/update_mode = 2 +audio_listener/enable_2d = false +audio_listener/enable_3d = false +physics/object_picking = false +gui/disable_input = false + +[node name="Spatial" parent="HBoxContainer_1/GestureArea_1/Viewport" instance=ExtResource( 2 )] + +[node name="Label3" type="Label" parent="HBoxContainer_1/GestureArea_1"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 15.0 +margin/top = 12.0 +margin/right = 55.0 +margin/bottom = 26.0 +text = "One-finger X/Y, two-finger Z + pinch" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + + diff --git a/misc/multitouch_cubes/engine.cfg b/misc/multitouch_cubes/engine.cfg new file mode 100644 index 00000000..9f65da3e --- /dev/null +++ b/misc/multitouch_cubes/engine.cfg @@ -0,0 +1,18 @@ +[application] + +name="InteractiveCubes" +main_scene="res://Main.tscn" +icon="res://icon.png" + +[display] + +width=512 +height=512 + +[physics_2d] + +motion_fix_enabled=true + +[render] + +default_clear_color=#ff1d2232 diff --git a/misc/multitouch_cubes/icon.png b/misc/multitouch_cubes/icon.png new file mode 100644 index 00000000..d9404909 Binary files /dev/null and b/misc/multitouch_cubes/icon.png differ diff --git a/misc/multitouch_cubes/icon.png.flags b/misc/multitouch_cubes/icon.png.flags new file mode 100644 index 00000000..5130fd1a --- /dev/null +++ b/misc/multitouch_cubes/icon.png.flags @@ -0,0 +1 @@ +gen_mipmaps=false diff --git a/misc/multitouch_view/Main.gd b/misc/multitouch_view/Main.gd new file mode 100644 index 00000000..12aa81be --- /dev/null +++ b/misc/multitouch_view/Main.gd @@ -0,0 +1,23 @@ +extends Node2D + +func _ready(): + set_process(true) + +func _process(delta): + # To keep redrawing on every frame + update() + +func _draw(): + # Get the touch helper singleton + var touch_helper = get_node("/root/TouchHelper") + # Draw every pointer as a circle + for ptr_id in touch_helper.state.keys(): + var pos = touch_helper.state[ptr_id] + var color = _get_color_for_ptr_id(ptr_id) + color.a = 0.75 + draw_circle(pos, 40.0, color) + +# Just a way of getting different colors +func _get_color_for_ptr_id(id): + var x = (id % 7) + 1 + return Color(float(bool(x & 1)), float(bool(x & 2)), float(bool(x & 4))) diff --git a/misc/multitouch_view/Main.tscn b/misc/multitouch_view/Main.tscn new file mode 100644 index 00000000..3e33eb6e --- /dev/null +++ b/misc/multitouch_view/Main.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=1] + +[ext_resource path="res://Main.gd" type="Script" id=1] + +[node name="Main" type="Node2D"] + +visibility/blend_mode = 1 +script/script = ExtResource( 1 ) + + diff --git a/misc/multitouch_view/TouchHelper.gd b/misc/multitouch_view/TouchHelper.gd new file mode 100644 index 00000000..1b40d52c --- /dev/null +++ b/misc/multitouch_view/TouchHelper.gd @@ -0,0 +1,45 @@ +# This will track the position of every pointer in its public `state` property, which is a +# Dictionary, in which each key is a pointer id (integer) and each value its position (Vector2). +# It works by listening to input events not handled by other means. +# It also remaps the pointer indices coming from the OS to the lowest available to be friendlier. +# It can be conveniently setup as a singleton. + +extends Node + +var state = {} +var _os2own = {} + +func _ready(): + set_process_unhandled_input(true) + +func _unhandled_input(event): + if event.type == InputEvent.SCREEN_TOUCH: + if event.pressed: + # Down + if !_os2own.has(event.index): # Defensively discard index if already known + var ptr_id = _find_free_pointer_id() + state[ptr_id] = Vector2(event.x, event.y) + _os2own[event.index] = ptr_id + else: + # Up + if _os2own.has(event.index): # Defensively discard index if not known + var ptr_id = _os2own[event.index] + state.erase(ptr_id) + _os2own.erase(event.index) + return true + + elif event.type == InputEvent.SCREEN_DRAG: + # Move + if _os2own.has(event.index): # Defensively discard index if not known + var ptr_id = _os2own[event.index] + state[ptr_id] = Vector2(event.x, event.y) + return true + + return false + +func _find_free_pointer_id(): + var used = state.keys() + var i = 0 + while i in used: + i += 1 + return i diff --git a/misc/multitouch_view/engine.cfg b/misc/multitouch_view/engine.cfg new file mode 100644 index 00000000..33bac062 --- /dev/null +++ b/misc/multitouch_view/engine.cfg @@ -0,0 +1,17 @@ +[application] + +name="TouchesView" +main_scene="res://Main.tscn" +icon="res://icon.png" + +[autoload] + +TouchHelper="*res://TouchHelper.gd" + +[physics_2d] + +motion_fix_enabled=true + +[render] + +default_clear_color=#ff1d2232 diff --git a/misc/multitouch_view/icon.png b/misc/multitouch_view/icon.png new file mode 100644 index 00000000..4daacd5b Binary files /dev/null and b/misc/multitouch_view/icon.png differ diff --git a/misc/multitouch_view/icon.png.flags b/misc/multitouch_view/icon.png.flags new file mode 100644 index 00000000..5130fd1a --- /dev/null +++ b/misc/multitouch_view/icon.png.flags @@ -0,0 +1 @@ +gen_mipmaps=false