12-09-2018, 04:45 PM
(This post was last modified: 12-09-2018, 04:54 PM by josemendez.)
(12-09-2018, 03:44 PM)arrnav96 Wrote: So if I use GetTouch, the dragging works, but it's glitchy. Also, let's say I have a touch count of two (two fingers on screen). On touching with both fingers, the slime actually gets pressed in the center of the distance between the two finger's positions. It's as if the code is averaging out the pressed point's location between all finger locations - resulting in only one "press" on screen. I know I'm going off topic, but I'm lost with multitouch right now.
Also, the touch itself is very glitchy. The "pressed" part constantly seems to vibrate while being dragged as shown in this video.
My guess is the script is getting confused between which particle to select according to ScreenToCamera point, amongst adjacent and close particles. I tried increasing the number of particles by setting up the SheetVHD mesh instead as a cloth, which has many more particles once initialized, but the vibration-on-touch effect is still visible.
These are my changes to the two scripts so far -
ObiClothPicker.cs -
Code:using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Obi {
public class ObiClothPicker : MonoBehaviour {
public class ParticlePickEventArgs : EventArgs {
public int particleIndex;
public Vector3 worldPosition;
public ParticlePickEventArgs(int particleIndex, Vector3 worldPosition) {
this.particleIndex = particleIndex;
this.worldPosition = worldPosition;
}
}
public event System.EventHandler<ParticlePickEventArgs> OnParticlePicked;
public event System.EventHandler<ParticlePickEventArgs> OnParticleHeld;
public event System.EventHandler<ParticlePickEventArgs> OnParticleDragged;
public event System.EventHandler<ParticlePickEventArgs> OnParticleReleased;
private MeshCollider meshCollider;
private ObiClothBase cloth;
private Mesh currentCollisionMesh;
private Vector3 lastMousePos = Vector3.zero;
private int pickedParticleIndex = -1;
private float pickedParticleDepth = 0;
public ObiClothBase Cloth {
get { return cloth; }
}
void Awake() {
cloth = GetComponent<ObiClothBase>();
lastMousePos = Input.mousePosition;
}
void OnEnable() {
// special case for skinned cloth, the collider must be added to the skeleton's root bone:
if (cloth is ObiCloth && ((ObiCloth)cloth).IsSkinned) {
SkinnedMeshRenderer sk = cloth.GetComponent<SkinnedMeshRenderer>();
if (sk != null && sk.rootBone != null) {
meshCollider = sk.rootBone.gameObject.AddComponent<MeshCollider>();
}
}
// regular cloth:
else {
meshCollider = gameObject.AddComponent<MeshCollider>();
}
// in case we were able to create the mesh collider, set it up:
if (meshCollider != null) {
meshCollider.enabled = false;
meshCollider.hideFlags = HideFlags.HideAndDontSave;
}
if (cloth != null)
cloth.Solver.OnFrameBegin += Cloth_Solver_OnFrameBegin;
}
void OnDisable() {
// destroy the managed mesh collider:
GameObject.Destroy(meshCollider);
if (cloth != null)
cloth.Solver.OnFrameBegin -= Cloth_Solver_OnFrameBegin;
}
void Cloth_Solver_OnFrameBegin(object sender, EventArgs e)
{
if (meshCollider == null)
return;
// Click:
#if UNITY_ANDROID
Touch myTouch = Input.GetTouch(0);
Touch[] myTouches = Input.touches;
for (int i = 0; i < Input.touchCount; i++)
{
meshCollider.enabled = true;
GameObject.Destroy(currentCollisionMesh);
currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
meshCollider.sharedMesh = currentCollisionMesh;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (meshCollider.Raycast(ray, out hitInfo, 100))
{
int[] tris = currentCollisionMesh.triangles;
Vector3[] vertices = currentCollisionMesh.vertices;
// find closest vertex in the triangle we just hit:
int closestVertex = -1;
float minDistance = float.MaxValue;
for (int j = 0; j < 3; ++j)
{
int vertex = tris[hitInfo.triangleIndex * 3 + j];
float distance = (vertices[vertex] - hitInfo.point).sqrMagnitude;
if (distance < minDistance)
{
minDistance = distance;
closestVertex = vertex;
}
}
// get particle index:
if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
{
pickedParticleIndex = cloth.topology.visualMap[closestVertex];
pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);
if (OnParticlePicked != null)
{
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticlePicked(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
}
}
}
meshCollider.enabled = false;
}
if (Input.touchCount < 1)
{
if (OnParticleReleased != null)
{
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
}
pickedParticleIndex = -1;
}
#endif
#if UNITY_EDITOR
if (Input.GetMouseButton(0))
{
meshCollider.enabled = true;
GameObject.Destroy(currentCollisionMesh);
currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
meshCollider.sharedMesh = currentCollisionMesh;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (meshCollider.Raycast(ray, out hitInfo, 100))
{
int[] tris = currentCollisionMesh.triangles;
Vector3[] vertices = currentCollisionMesh.vertices;
// find closest vertex in the triangle we just hit:
int closestVertex = -1;
float minDistance = float.MaxValue;
for (int j = 0; j < 3; ++j)
{
int vertex = tris[hitInfo.triangleIndex * 3 + j];
float distance = (vertices[vertex] - hitInfo.point).sqrMagnitude;
if (distance < minDistance)
{
minDistance = distance;
closestVertex = vertex;
}
}
// get particle index:
if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
{
pickedParticleIndex = cloth.topology.visualMap[closestVertex];
pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);
if (OnParticlePicked != null)
{
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticlePicked(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
}
}
}
meshCollider.enabled = false;
}
else if (pickedParticleIndex >= 0) {
// Drag:
/*Vector3 mouseDelta = Input.mousePosition - lastMousePos;
if (mouseDelta.magnitude > 0.01f && OnParticleDragged != null){
Vector3 worldPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticleDragged(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));
}else if (OnParticleHeld != null){
Vector3 worldPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticleHeld(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));
}*/
// Release:
if (Input.GetMouseButtonUp(0)) {
if (OnParticleReleased != null) {
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
}
pickedParticleIndex = -1;
}
}
lastMousePos = Input.mousePosition;
#endif
}
}
}
ObiClothDragger.cs -
Code:using System;
using UnityEngine;
namespace Obi
{
[RequireComponent(typeof(ObiClothPicker))]
public class ObiClothDragger : MonoBehaviour
{
public float springStiffness = 50;
public float springDamping = 1;
private ObiClothPicker picker;
private ObiClothPicker.ParticlePickEventArgs pickArgs;
void OnEnable()
{
picker = GetComponent<ObiClothPicker>();
picker.OnParticlePicked += Picker_OnParticleDragged;
picker.OnParticleDragged += Picker_OnParticleDragged;
picker.OnParticleReleased += Picker_OnParticleReleased;
}
void OnDisable()
{
picker.OnParticlePicked -= Picker_OnParticleDragged;
picker.OnParticleDragged -= Picker_OnParticleDragged;
picker.OnParticleReleased -= Picker_OnParticleReleased;
}
void FixedUpdate ()
{
if (pickArgs != null){
ObiSolver solver = picker.Cloth.Solver;
// Calculate picking position in solver space:
Vector3 targetPosition = pickArgs.worldPosition;
if (solver.simulateInLocalSpace)
targetPosition = solver.transform.InverseTransformPoint(targetPosition);
// Get particle position and velocity:
Vector4[] positions = new Vector4[1];
Vector4[] velocities = new Vector4[1];
int solverIndex = picker.Cloth.particleIndices[pickArgs.particleIndex];
Oni.GetParticlePositions(solver.OniSolver,positions,1,solverIndex);
Oni.GetParticleVelocities(solver.OniSolver,velocities,1,solverIndex);
// Calculate effective inverse mass:
float invMass = picker.Cloth.invMasses[pickArgs.particleIndex] * picker.Cloth.areaContribution[pickArgs.particleIndex];
if (invMass > 0){
// Calculate and apply spring force:
Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1],targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);
Oni.AddParticleExternalForce(picker.Cloth.Solver.OniSolver,ref force,new int[]{solverIndex},1);
}
}
}
void Picker_OnParticleDragged (object sender, ObiClothPicker.ParticlePickEventArgs e)
{
pickArgs = e;
}
void Picker_OnParticleReleased (object sender, ObiClothPicker.ParticlePickEventArgs e)
{
pickArgs = null;
}
}
}
Lastly, in ObiClothDragger.cs, in fixed update, while calculating and applying spring force, I tried changing the line -
Code:Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1],targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);
to this...
Code:Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1]-10,targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);
..so that I could provide an increased depth effect at the point of touch (instead of using a splat map, to get required "pressed" visual effect). But it makes the whole cloth glitch out and disappear instantly. How do I increase the depth of the "press"? For now I was assuming "targetPosition[1]" is the Y-Position of the press.
Hi,
There's lots of things off or downright wrong here, most of them related to basic programming concepts or Unity API usage. I'm afraid I cannot offer support beyond this, as it is not Obi-related. However I will try to outline some of the issues to help you out:
Code:
for (int i = 0; i < Input.touchCount; i++)
{
meshCollider.enabled = true;
GameObject.Destroy(currentCollisionMesh);
currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
meshCollider.sharedMesh = currentCollisionMesh;
....
meshCollider.enabled = false;
}
#endif
Here you're destroying and re-creating the mesh collider used for raycast and input detection for each finger. If you have 3 fingers touching the screen, you're destroying and instantiating the entire mesh 3 times per frame. While technically ok, this is terrible for performance. You should start the for loop after creating the mesh just once.
Code:
if (Input.touchCount < 1)
{
if (OnParticleReleased != null)
{
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
}
pickedParticleIndex = -1;
}
Here you're calling OnParticleReleased just once, after the user has lifted all fingers from the screen. This should be called once for each finger that got lifted off.
Code:
// get particle index:
if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
{
pickedParticleIndex = cloth.topology.visualMap[closestVertex];
pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);
There's a deeper issue: you should have arrays (pickedParticleIndices[] and pickedParticleDepths[]) to support multitouch, instead of a single variable. How else can you identify each particle when multiple ones are being dragged? The way the code is written, the last finger to touch the screen will overwrite pickedParticleIndex. Only one particle will be picked.
Code:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
In this line and several others, you're still using Input.mousePosition, mixing it with Input.touchCount. You should use Input.GetTouch(index) instead (see https://docs.unity3d.com/ScriptReference/Touch.html), as the mousePosition returns the average of all finger positions when on a touchscreen (read Unity's documentation), which is precisely the result you're getting.