From b6db599fa12588b30e5d3d54ffb6cdb4639053ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Thu, 7 Dec 2017 03:42:48 +0100 Subject: [PATCH] Examples about (multi)touch events --- misc/multitouch_cubes/CubeScene.tscn | 151 ++++++++++++++++ misc/multitouch_cubes/GestureArea.gd | 133 ++++++++++++++ misc/multitouch_cubes/Main.tscn | 256 +++++++++++++++++++++++++++ misc/multitouch_cubes/engine.cfg | 18 ++ misc/multitouch_cubes/icon.png | Bin 0 -> 3572 bytes misc/multitouch_cubes/icon.png.flags | 1 + misc/multitouch_view/Main.gd | 23 +++ misc/multitouch_view/Main.tscn | 10 ++ misc/multitouch_view/TouchHelper.gd | 45 +++++ misc/multitouch_view/engine.cfg | 17 ++ misc/multitouch_view/icon.png | Bin 0 -> 1524 bytes misc/multitouch_view/icon.png.flags | 1 + 12 files changed, 655 insertions(+) create mode 100644 misc/multitouch_cubes/CubeScene.tscn create mode 100644 misc/multitouch_cubes/GestureArea.gd create mode 100644 misc/multitouch_cubes/Main.tscn create mode 100644 misc/multitouch_cubes/engine.cfg create mode 100644 misc/multitouch_cubes/icon.png create mode 100644 misc/multitouch_cubes/icon.png.flags create mode 100644 misc/multitouch_view/Main.gd create mode 100644 misc/multitouch_view/Main.tscn create mode 100644 misc/multitouch_view/TouchHelper.gd create mode 100644 misc/multitouch_view/engine.cfg create mode 100644 misc/multitouch_view/icon.png create mode 100644 misc/multitouch_view/icon.png.flags 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 0000000000000000000000000000000000000000..d940490921c1883d0551cb270bf88516d45c2e67 GIT binary patch literal 3572 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