Files
RaindropViewer/Assets/Raindrop/Render/RenderAvatar.cs
T

2289 lines
84 KiB
C#

/**
* Radegast Metaverse Client
* Copyright(c) 2009-2014, Radegast Development Team
* Copyright(c) 2016-2020, Sjofn, LLC
* All rights reserved.
*
* Radegast is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.If not, see<https://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using OpenMetaverse;
using OpenMetaverse.Rendering;
using Path = System.IO.Path;
namespace Raindrop.Rendering
{
public class attachment_point
{
public string name;
public string joint;
public string location;
public Vector3 position;
public Quaternion rotation;
public int id;
public int group;
public GLMesh jointmesh;
public int jointmeshindex;
public attachment_point(XmlNode node)
{
name = node.Attributes.GetNamedItem("name").Value;
joint = node.Attributes.GetNamedItem("joint").Value;
location = node.Attributes.GetNamedItem("location").Value;
position = VisualParamEx.XmlParseVector(node.Attributes.GetNamedItem("position").Value);
rotation = VisualParamEx.XmlParseRotation(node.Attributes.GetNamedItem("rotation").Value);
id = Int32.Parse(node.Attributes.GetNamedItem("id").Value);
group = Int32.Parse(node.Attributes.GetNamedItem("group").Value);
}
}
/// <summary>
/// Subclass of LindenMesh that adds vertex, index, and texture coordinate
/// arrays suitable for pushing direct to OpenGL
/// </summary>
public class GLMesh : LindenMesh
{
/// <summary>
/// Subclass of LODMesh that adds an index array suitable for pushing
/// direct to OpenGL
/// </summary>
///
public int teFaceID;
public Dictionary<int, VisualParamEx> _evp = new Dictionary<int, VisualParamEx>();
public new class LODMesh : ReferenceMesh
{
public ushort[] Indices;
public override void LoadMesh(string filename)
{
base.LoadMesh(filename);
// Generate the index array
Indices = new ushort[NumFaces * 3];
int current = 0;
for (int i = 0; i < NumFaces; i++)
{
Indices[current++] = (ushort)Faces[i].Indices[0];
Indices[current++] = (ushort)Faces[i].Indices[1];
Indices[current++] = (ushort)Faces[i].Indices[2];
}
}
}
public struct GLData
{
public float[] Vertices;
public float[] Normals;
public ushort[] Indices;
public float[] TexCoords;
public Vector3 Center;
public float[] weights; //strictly these are constant and don't need instancing with the GLMesh
public string[] skinJoints; //strictly these are constant and don't need instancing with the GLMesh
}
public static GLData baseRenderData;
public GLData RenderData;
public GLData OrigRenderData;
public GLData MorphRenderData;
public GLAvatar av;
public GLMesh(string name)
: base(name)
{
}
public GLMesh(GLMesh source, GLAvatar av)
: base(source.Name)
{
this.av = av;
// Make a new GLMesh copy from the supplied source
RenderData.Vertices = new float[source.RenderData.Vertices.Length];
RenderData.Normals = new float[source.RenderData.Normals.Length];
RenderData.TexCoords = new float[source.RenderData.TexCoords.Length];
RenderData.Indices = new ushort[source.RenderData.Indices.Length];
RenderData.weights = new float[source.RenderData.weights.Length];
RenderData.skinJoints = new string[source.RenderData.skinJoints.Length];
Array.Copy(source.RenderData.Vertices, RenderData.Vertices, source.RenderData.Vertices.Length);
Array.Copy(source.RenderData.Normals, RenderData.Normals, source.RenderData.Normals.Length);
Array.Copy(source.RenderData.TexCoords, RenderData.TexCoords, source.RenderData.TexCoords.Length);
Array.Copy(source.RenderData.Indices, RenderData.Indices, source.RenderData.Indices.Length);
Array.Copy(source.RenderData.weights, RenderData.weights, source.RenderData.weights.Length);
Array.Copy(source.RenderData.skinJoints, RenderData.skinJoints, source.RenderData.skinJoints.Length);
RenderData.Center = new Vector3(source.RenderData.Center);
teFaceID = source.teFaceID;
RotationAngles = new Vector3(source.RotationAngles);
Scale = new Vector3(source.Scale);
Position = new Vector3(source.Position);
// We should not need to instance these the reference from the top should be constant
_evp = source._evp;
Morphs = source.Morphs;
OrigRenderData.Indices = new ushort[source.RenderData.Indices.Length];
OrigRenderData.TexCoords = new float[source.RenderData.TexCoords.Length];
OrigRenderData.Vertices = new float[source.RenderData.Vertices.Length];
OrigRenderData.Normals = new float[source.RenderData.Normals.Length];
MorphRenderData.Vertices = new float[source.RenderData.Vertices.Length];
MorphRenderData.Normals = new float[source.RenderData.Normals.Length];
Array.Copy(source.RenderData.Vertices, OrigRenderData.Vertices, source.RenderData.Vertices.Length);
Array.Copy(source.RenderData.Vertices, MorphRenderData.Vertices, source.RenderData.Vertices.Length);
Array.Copy(source.RenderData.Normals, OrigRenderData.Normals, source.RenderData.Normals.Length);
Array.Copy(source.RenderData.Normals, MorphRenderData.Normals, source.RenderData.Normals.Length);
Array.Copy(source.RenderData.TexCoords, OrigRenderData.TexCoords, source.RenderData.TexCoords.Length);
Array.Copy(source.RenderData.Indices, OrigRenderData.Indices, source.RenderData.Indices.Length);
}
public void setMeshPos(Vector3 pos)
{
Position = pos;
// Force offset all vertices by this offset
// this is required to force some meshes into the default T bind pose
for (int vert = 0; vert < RenderData.Vertices.Length; vert = vert + 3)
{
RenderData.Vertices[vert] += pos.X;
RenderData.Vertices[vert + 1] += pos.Y;
RenderData.Vertices[vert + 2] += pos.Z;
}
}
public void setMeshRot(Vector3 rot)
{
RotationAngles = rot;
}
public override void LoadMesh(string filename)
{
base.LoadMesh(filename);
float minY, minZ;
var minX = minY = minZ = float.MaxValue;
float maxY, maxZ;
var maxX = maxY = maxZ = float.MinValue;
// Generate the vertex array
RenderData.Vertices = new float[NumVertices * 3];
RenderData.Normals = new float[NumVertices * 3];
Quaternion quat = Quaternion.CreateFromEulers(0, 0, (float)(Math.PI / 4.0));
int current = 0;
for (int i = 0; i < NumVertices; i++)
{
RenderData.Normals[current] = Vertices[i].Normal.X;
RenderData.Vertices[current++] = Vertices[i].Coord.X;
RenderData.Normals[current] = Vertices[i].Normal.Y;
RenderData.Vertices[current++] = Vertices[i].Coord.Y;
RenderData.Normals[current] = Vertices[i].Normal.Z;
RenderData.Vertices[current++] = Vertices[i].Coord.Z;
if (Vertices[i].Coord.X < minX)
minX = Vertices[i].Coord.X;
else if (Vertices[i].Coord.X > maxX)
maxX = Vertices[i].Coord.X;
if (Vertices[i].Coord.Y < minY)
minY = Vertices[i].Coord.Y;
else if (Vertices[i].Coord.Y > maxY)
maxY = Vertices[i].Coord.Y;
if (Vertices[i].Coord.Z < minZ)
minZ = Vertices[i].Coord.Z;
else if (Vertices[i].Coord.Z > maxZ)
maxZ = Vertices[i].Coord.Z;
}
// Calculate the center-point from the bounding box edges
RenderData.Center = new Vector3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
// Generate the index array
RenderData.Indices = new ushort[NumFaces * 3];
current = 0;
for (int i = 0; i < NumFaces; i++)
{
RenderData.Indices[current++] = (ushort)Faces[i].Indices[0];
RenderData.Indices[current++] = (ushort)Faces[i].Indices[1];
RenderData.Indices[current++] = (ushort)Faces[i].Indices[2];
}
// Generate the texcoord array
RenderData.TexCoords = new float[NumVertices * 2];
current = 0;
for (int i = 0; i < NumVertices; i++)
{
RenderData.TexCoords[current++] = Vertices[i].TexCoord.X;
RenderData.TexCoords[current++] = Vertices[i].TexCoord.Y;
}
RenderData.weights = new float[NumVertices];
for (int i = 0; i < NumVertices; i++)
{
RenderData.weights[i] = Vertices[i].Weight;
}
RenderData.skinJoints = new string[SkinJoints.Length + 3];
for (int i = 1; i < SkinJoints.Length; i++)
{
RenderData.skinJoints[i] = SkinJoints[i];
}
}
public new void LoadLODMesh(int level, string filename)
{
LODMesh lod = new LODMesh();
lod.LoadMesh(filename);
LodMeshes[level] = lod;
}
public void applyjointweights()
{
/*Each weight actually contains two pieces of information.
* The number to the left of the decimal point is the index of the joint and also
* implicitly indexes to the following joint. The actual weight is to the right of
* the decimal point and interpolates between these two joints. The index is into an
* "expanded" list of joints, not just a linear array of the joints as defined in the
* skeleton file. In particular, any joint that has more than one child will be repeated
* in the list for each of its children.
*/
float weight = -9999;
int jointindex = 0;
float factor;
Bone ba = null;
Bone bb = null;
for (int v = 0, x = 0; v < RenderData.Vertices.Length; v = v + 3, x++)
{
if (weight != RenderData.weights[x])
{
jointindex = (int)Math.Floor(weight = RenderData.weights[x]);
factor = RenderData.weights[x] - jointindex;
weight = weight - jointindex;
string jointname = "", jointname2 = "";
switch (Name)
{
case "upperBodyMesh":
jointname = skeleton.mUpperMeshMapping[jointindex];
jointindex++;
jointname2 = skeleton.mUpperMeshMapping[jointindex];
break;
case "lowerBodyMesh":
jointname = skeleton.mLowerMeshMapping[jointindex];
jointindex++;
jointname2 = skeleton.mLowerMeshMapping[jointindex];
break;
case "headMesh":
jointname = skeleton.mHeadMeshMapping[jointindex];
jointindex++;
jointname2 = skeleton.mHeadMeshMapping[jointindex];
break;
case "eyeBallRightMesh":
jointname = "mHead";
jointname2 = "mEyeRight";
break;
case "eyeBallLeftMesh":
jointname = "mHead";
jointname2 = "mEyeLeft";
break;
case "eyelashMesh":
case "hairMesh":
jointname = "mHead";
jointname2 = "mSkull";
break;
default:
return;
}
ba = av.skel.mBones[jointname];
bb = jointname2 == "" ? null : av.skel.mBones[jointname2];
}
//Special cases 0 is not used
// ON upper torso 5 and 10 are not used
// 4 is neck and 6 and 11 are the left and right collar bones
//TODO use the SRT matrix to do this!
Vector3 lerpa;
Vector3 offseta;
Quaternion rota;
Vector3 lerpb;
Vector3 offsetb;
Quaternion rotb;
Vector3 pos;
Vector3 posa = new Vector3(MorphRenderData.Vertices[v], MorphRenderData.Vertices[v + 1], MorphRenderData.Vertices[v + 2]);
if (bb != null)
{
lerpa = ba.getDeltaOffset();
offseta = ba.getTotalOffset();
rota = ba.getTotalRotation();
lerpb = bb.getDeltaOffset();
offsetb = bb.getTotalOffset();
rotb = bb.getTotalRotation();
Vector3 posb = new Vector3(MorphRenderData.Vertices[v], MorphRenderData.Vertices[v + 1], MorphRenderData.Vertices[v + 2]);
//move back to mesh local coords
posa = posa - offseta;
// apply rotated offset
posa = ((posa + lerpa) * ba.scale) * rota;
//move back to avatar local coords
posa = posa + offseta;
//move back to mesh local coords
posb = posb - offsetb;
// apply rotated offset
posb = ((posb + lerpb) * bb.scale) * rotb;
//move back to avatar local coords
posb = posb + offsetb;
//LERP the two points to produce smooth skinning ;-)
pos = Vector3.Lerp(posa, posb, weight);
}
else
{
lerpa = ba.getDeltaOffset();
offseta = ba.getTotalOffset();
rota = ba.getTotalRotation();
//move back to mesh local coords
posa = posa - offseta;
// apply rotated offset
posa = ((posa + lerpa) * ba.scale) * rota;
//move back to avatar local coords
posa = posa + offseta;
//Only one bone contributing so its 100% that one.
pos = posa;
}
RenderData.Vertices[v] = pos.X;
RenderData.Vertices[v + 1] = pos.Y;
RenderData.Vertices[v + 2] = pos.Z;
}
}
public void resetallmorphs()
{
for (int i = 0; i < OrigRenderData.Vertices.Length / 3; i++)
{
MorphRenderData.Vertices[i * 3] = OrigRenderData.Vertices[i * 3];
MorphRenderData.Vertices[(i * 3) + 1] = OrigRenderData.Vertices[i * 3 + 1];
MorphRenderData.Vertices[(i * 3) + 2] = OrigRenderData.Vertices[i * 3 + 2];
MorphRenderData.Normals[i * 3] = OrigRenderData.Normals[i * 3];
MorphRenderData.Normals[(i * 3) + 1] = OrigRenderData.Normals[i * 3 + 1];
MorphRenderData.Normals[(i * 3) + 2] = OrigRenderData.Normals[i * 3 + 2];
RenderData.TexCoords[i * 2] = OrigRenderData.TexCoords[i * 2];
RenderData.TexCoords[(i * 2) + 1] = OrigRenderData.TexCoords[i * 2 + 1];
}
}
public void morphmesh(Morph morph, float weight)
{
// Logger.Log(String.Format("Applying morph {0} weight {1}",morph.Name,weight),Helpers.LogLevel.Debug);
for (int v = 0; v < morph.NumVertices; v++)
{
MorphVertex mvx = morph.Vertices[v];
uint i = mvx.VertexIndex;
MorphRenderData.Vertices[i * 3] = MorphRenderData.Vertices[i * 3] + mvx.Coord.X * weight;
MorphRenderData.Vertices[(i * 3) + 1] = MorphRenderData.Vertices[i * 3 + 1] + mvx.Coord.Y * weight;
MorphRenderData.Vertices[(i * 3) + 2] = MorphRenderData.Vertices[i * 3 + 2] + mvx.Coord.Z * weight;
MorphRenderData.Normals[i * 3] = MorphRenderData.Normals[i * 3] + mvx.Normal.X * weight;
MorphRenderData.Normals[(i * 3) + 1] = MorphRenderData.Normals[(i * 3) + 1] + mvx.Normal.Y * weight;
MorphRenderData.Normals[(i * 3) + 2] = MorphRenderData.Normals[(i * 3) + 2] + mvx.Normal.Z * weight;
RenderData.TexCoords[i * 2] = OrigRenderData.TexCoords[i * 2] + mvx.TexCoord.X * weight;
RenderData.TexCoords[(i * 2) + 1] = OrigRenderData.TexCoords[i * 2 + 1] + mvx.TexCoord.Y * weight;
}
}
}
public class GLAvatar
{
private static Dictionary<string, GLMesh> _defaultmeshes = new Dictionary<string, GLMesh>();
public Dictionary<string, GLMesh> _meshes = new Dictionary<string, GLMesh>();
public skeleton skel = new skeleton();
public static Dictionary<int, attachment_point> attachment_points = new Dictionary<int, attachment_point>();
public bool _wireframe = true;
public bool _showSkirt = false;
public VisualParamEx.EparamSex msex;
public byte[] VisualAppearanceParameters = new byte[1024];
static bool lindenMeshesLoaded = false;
public GLAvatar()
{
lock (_defaultmeshes) foreach (var kvp in _defaultmeshes)
{
GLMesh mesh = new GLMesh(kvp.Value, this); // Instance our meshes
_meshes.Add(kvp.Key, mesh);
}
}
public static void dumptweaks()
{
for (int x = 0; x < VisualParamEx.TweakableParams.Count; x++)
{
VisualParamEx vpe = (VisualParamEx)VisualParamEx.TweakableParams.GetByIndex(x);
Console.WriteLine($"{x} is {vpe.Name}");
}
}
public static void loadlindenmeshes2(string LODfilename)
{
// Already have mashes loaded?
if (lindenMeshesLoaded) return;
attachment_points.Clear();
string basedir = Path.Combine(Directory.GetCurrentDirectory(), "character");
XmlDocument lad = new XmlDocument();
lad.Load(Path.Combine(basedir, LODfilename));
// First, read the skeleton section this contains attachment point info and the bone deform info for visual params
// And load the skeleton file in to the bones class
XmlNodeList skeleton = lad.GetElementsByTagName("skeleton");
string skeletonfilename = skeleton[0].Attributes.GetNamedItem("file_name").Value;
Bone.loadbones(skeletonfilename);
// Next read all the skeleton child nodes, we have attachment points and bone deform params
// attachment points are an offset and rotation from a bone location
// the name of the bone they reference is the joint paramater
// params in the skeleton nodes are bone deforms, eg leg length changes the scale of the leg bones
foreach (XmlNode skeletonnode in skeleton[0].ChildNodes)
{
if (skeletonnode.Name == "attachment_point")
{
attachment_point point = new attachment_point(skeletonnode);
attachment_points.Add(point.id, point);
}
}
// Parse all visual paramaters in one go
// we can deduce type on the fly
XmlNodeList paramss = lad.GetElementsByTagName("param");
foreach (XmlNode paramNode in paramss)
{
VisualParamEx vp = new VisualParamEx(paramNode);
}
//Now we parse the mesh nodes, mesh nodes reference a particular LLM file with a LOD
XmlNodeList meshes = lad.GetElementsByTagName("mesh");
foreach (XmlNode meshNode in meshes)
{
string type = meshNode.Attributes.GetNamedItem("type").Value;
int lod = Int32.Parse(meshNode.Attributes.GetNamedItem("lod").Value);
string fileName = meshNode.Attributes.GetNamedItem("file_name").Value;
GLMesh mesh = null;
lock (_defaultmeshes)
mesh = (_defaultmeshes.ContainsKey(type) ? _defaultmeshes[type] : new GLMesh(type));
// Set up the texture elements for each mesh
// And hack the eyeball position
switch (mesh.Name)
{
case "lowerBodyMesh":
mesh.teFaceID = (int)AvatarTextureIndex.LowerBaked;
break;
case "upperBodyMesh":
mesh.teFaceID = (int)AvatarTextureIndex.UpperBaked;
break;
case "headMesh":
mesh.teFaceID = (int)AvatarTextureIndex.HeadBaked;
break;
case "hairMesh":
mesh.teFaceID = (int)AvatarTextureIndex.HairBaked;
break;
case "eyelashMesh":
mesh.teFaceID = (int)AvatarTextureIndex.HeadBaked;
break;
case "eyeBallRightMesh":
mesh.teFaceID = (int)AvatarTextureIndex.EyesBaked;
break;
case "eyeBallLeftMesh":
mesh.teFaceID = (int)AvatarTextureIndex.EyesBaked;
break;
case "skirtMesh":
mesh.teFaceID = (int)AvatarTextureIndex.SkirtBaked;
break;
default:
mesh.teFaceID = 0;
break;
}
if (lod == 0)
mesh.LoadMesh(Path.Combine(basedir, fileName));
else
mesh.LoadLODMesh(lod, Path.Combine(basedir, fileName));
if (lod == 0)
{
switch (mesh.Name)
{
case "eyeBallLeftMesh":
lock (Bone.mBones) mesh.setMeshPos(Bone.mBones["mEyeLeft"].getTotalOffset());
break;
case "eyeBallRightMesh":
lock (Bone.mBones) mesh.setMeshPos(Bone.mBones["mEyeRight"].getTotalOffset());
break;
case "eyelashMesh":
lock (Bone.mBones) mesh.setMeshPos(Bone.mBones["mHead"].getTotalOffset());
break;
case "hairMesh":
//mesh.setMeshPos(Bone.mBones["mHead"].getTotalOffset());
break;
}
}
lock (_defaultmeshes) _defaultmeshes[type] = mesh;
}
lindenMeshesLoaded = true;
}
public void applyMorph(Avatar av, int param, float weight)
{
VisualParamEx vpx;
if (VisualParamEx.AllParams.TryGetValue(param, out vpx))
{
applyMorph(vpx, av, weight);
// a morph ID may apply to more than one mesh (duplicate VP IDs)
// in this case also apply to all other identical IDs
foreach (VisualParamEx cvpx in vpx.IdenticalIds)
{
applyMorph(cvpx, av, weight);
}
}
}
public void applyMorph(VisualParamEx vpx, Avatar av, float weight)
{
weight = Utils.Clamp(weight, 0.0f, 1.0f);
float value = Utils.Lerp(vpx.MinValue, vpx.MaxValue, weight);
// don't do anything for less than 1% change
if (value > -0.001 && value < 0.001)
return;
// Morphs are mesh deforms
if (vpx.pType == VisualParamEx.ParamType.TYPE_MORPH)
{
// Its a morph
GLMesh mesh;
if (_meshes.TryGetValue(vpx.MorphMesh, out mesh))
{
foreach (LindenMesh.Morph morph in mesh.Morphs) //optimise me to a dictionary
{
if (morph.Name == vpx.Name)
{
if (mesh.Name == "skirtMesh" && _showSkirt == false)
return;
// Logger.Log(String.Format("Applying morph {0} ID {2} weight {1} mesh {3}",morph.Name,weight,vpx.ParamID,mesh.Name),Helpers.LogLevel.Debug);
mesh.morphmesh(morph, value);
}
}
}
}
// Driver type
// A driver drives multiple slave visual paramaters
if (vpx.pType == VisualParamEx.ParamType.TYPE_DRIVER)
{
foreach (VisualParamEx.driven child in vpx.ChildParams)
{
/***** BEGIN UNGRACEFULL CODE STEALING ******/
// driven ________
// ^ /| |\ ^
// | / | | \ |
// | / | | \ |
// | / | | \ |
// | / | | \ |
//-------|----|-------|----|-------> driver
// | min1 max1 max2 min2
if (child.hasMinMax == false)
{
applyMorph(av, child.id, weight);
continue;
}
float driven_weight = vpx.DefaultValue;
float driven_max = VisualParamEx.AllParams[child.id].MaxValue;
float driven_min = VisualParamEx.AllParams[child.id].MinValue;
float input_weight = weight;
float min_weight = vpx.MinValue;
float max_weight = vpx.MaxValue;
if (input_weight <= child.min1)
{
if (child.min1 == child.max1 &&
child.min1 <= min_weight)
{
driven_weight = driven_max;
}
else
{
driven_weight = driven_min;
}
}
else
{
if (input_weight <= child.max1)
{
float t = (input_weight - child.min1)/(child.max1 - child.min1);
driven_weight = driven_min + t*(driven_max - driven_min);
}
else if (input_weight <= child.max2)
{
driven_weight = driven_max;
}
else if (input_weight <= child.min2)
{
float t = (input_weight - child.max2)/(child.min2 - child.max2);
driven_weight = driven_max + t*(driven_min - driven_max);
}
else
{
driven_weight = child.max2 >= max_weight ? driven_max : driven_min;
}
}
/***** END UNGRACEFULL CODE STEALING ******/
applyMorph(av, child.id, driven_weight);
}
return;
}
//Is this a bone deform?
if (vpx.pType == VisualParamEx.ParamType.TYPE_BONEDEFORM)
{
// scale="0 0 .3" />
// value_min="-1"
// value_max="1"
foreach (var kvp in vpx.BoneDeforms)
{
skel.scalebone(kvp.Key, Vector3.One + (kvp.Value.scale * value));
skel.offsetbone(kvp.Key, kvp.Value.offset * value);
}
}
}
public void morph(Avatar av)
{
if (av.VisualParameters == null)
return;
ThreadPool.QueueUserWorkItem(sync =>
{
int x = 0;
// We need a lock here as we may get multiple packets thrown at us and we should at least
// process them in turn not 1/2 process one then start to process the next.
// That said av might not be the best lock object, but it will do for the moment
lock (av)
{
if (av.VisualParameters.Length > 123)
{
msex = av.VisualParameters[31] > 127 ? VisualParamEx.EparamSex.SEX_MALE : VisualParamEx.EparamSex.SEX_FEMALE;
}
foreach (GLMesh mesh in _meshes.Values)
{
mesh.resetallmorphs();
}
skel.resetbonescales();
foreach (byte vpvalue in av.VisualParameters)
{
if (x >= VisualParamEx.TweakableParams.Count)
{
//Logger.Log("Two many visual paramaters in Avatar appearance", Helpers.LogLevel.Warning);
break;
}
VisualParamEx vpe = (VisualParamEx)VisualParamEx.TweakableParams.GetByIndex(x);
if (vpe.sex != VisualParamEx.EparamSex.SEX_BOTH && vpe.sex != msex)
{
x++;
continue;
}
VisualAppearanceParameters[x] = vpvalue;
float value = (vpvalue / 255.0f);
applyMorph(av, vpe.ParamID, value);
x++;
}
skel.mNeedsMeshRebuild = true;
// Don't update actual meshes here anymore, we do it every frame because of animation anyway
}
});
}
}
public class joint
{
public Vector3 offset;
public Quaternion rotation;
}
public class animationwrapper
{
public BinBVHAnimationReader anim;
public float mRunTime;
public UUID mAnimation;
public bool mPotentialyDead = false;
public enum animstate
{
STATE_WAITINGASSET,
STATE_EASEIN,
STATE_EASEOUT,
STATE_PLAY,
STATE_STOP
}
public animstate playstate;
public animationwrapper(BinBVHAnimationReader anim)
{
this.anim = anim;
playstate = animstate.STATE_EASEIN;
mRunTime = 0;
}
public animationwrapper(UUID key)
{
mAnimation = key;
playstate = animstate.STATE_WAITINGASSET;
mRunTime = 0;
}
public void stopanim()
{
//Logger.Log(string.Format("Animation {0} marked as stopped",mAnimation),Helpers.LogLevel.Info);
playstate = animstate.STATE_STOP;
}
}
public class skeleton
{
public Dictionary<string, Bone> mBones;
private Dictionary<string, int> mPriority = new Dictionary<string, int>();
private Dictionary<string, joint> jointdeforms = new Dictionary<string, joint>();
public static Dictionary<int, string> mUpperMeshMapping = new Dictionary<int, string>();
public static Dictionary<int, string> mLowerMeshMapping = new Dictionary<int, string>();
public static Dictionary<int, string> mHeadMeshMapping = new Dictionary<int, string>();
// public List<BinBVHAnimationReader> mAnimations = new List<BinBVHAnimationReader>();
public Dictionary<UUID, animationwrapper> mAnimationsWrapper = new Dictionary<UUID, animationwrapper>();
public static Dictionary<UUID, RenderAvatar> mAnimationTransactions = new Dictionary<UUID, RenderAvatar>();
public static Dictionary<UUID, BinBVHAnimationReader> mAnimationCache = new Dictionary<UUID, BinBVHAnimationReader>();
public bool mNeedsUpdate = false;
public bool mNeedsMeshRebuild = true;
public struct binBVHJointState
{
public float currenttime_rot;
public int lastkeyframe_rot;
public int nextkeyframe_rot;
public float currenttime_pos;
public int lastkeyframe_pos;
public int nextkeyframe_pos;
public int pos_loopinframe;
public int pos_loopoutframe;
public int rot_loopinframe;
public int rot_loopoutframe;
public Quaternion easeoutrot;
public float easeoutfactor;
}
public skeleton()
{
mBones = new Dictionary<string, Bone>();
lock (Bone.mBones) foreach (Bone src in Bone.mBones.Values)
{
Bone newbone = new Bone(src);
mBones.Add(newbone.name, newbone);
}
//rebuild the skeleton structure on the new copy
foreach (Bone src in mBones.Values)
{
if (src.mParentBone != null)
{
src.parent = mBones[src.mParentBone];
src.parent.children.Add(src);
}
}
//FUDGE
if (mUpperMeshMapping.Count == 0)
{
mUpperMeshMapping.Add(1, "mPelvis");
mUpperMeshMapping.Add(2, "mTorso");
mUpperMeshMapping.Add(3, "mChest");
mUpperMeshMapping.Add(4, "mNeck");
mUpperMeshMapping.Add(5, "mNeck");
mUpperMeshMapping.Add(6, "mCollarLeft");
mUpperMeshMapping.Add(7, "mShoulderLeft");
mUpperMeshMapping.Add(8, "mElbowLeft");
mUpperMeshMapping.Add(9, "mWristLeft");
mUpperMeshMapping.Add(10, "mNeck"); // this case might fail for mWriteLeft and mNeck acting together?
mUpperMeshMapping.Add(11, "mCollarRight");
mUpperMeshMapping.Add(12, "mShoulderRight");
mUpperMeshMapping.Add(13, "mElbowRight");
mUpperMeshMapping.Add(14, "mWristRight");
mUpperMeshMapping.Add(15, "");
mLowerMeshMapping.Add(1, "mPelvis");
mLowerMeshMapping.Add(2, "mHipRight");
mLowerMeshMapping.Add(3, "mKneeRight");
mLowerMeshMapping.Add(4, "mAnkleRight");
mLowerMeshMapping.Add(5, "mPelvis");
mLowerMeshMapping.Add(6, "mHipLeft");
mLowerMeshMapping.Add(7, "mKneeLeft");
mLowerMeshMapping.Add(8, "mAnkleLeft");
mLowerMeshMapping.Add(9, "");
mHeadMeshMapping.Add(1, "mNeck");
mHeadMeshMapping.Add(2, "mHead");
mHeadMeshMapping.Add(3, "");
}
}
public void processAnimation(UUID mAnimID)
{
if (mAnimationsWrapper.ContainsKey(mAnimID))
{
// Its an existing animation, we may need to do a seq update but we don't yet
// support that
mAnimationsWrapper[mAnimID].playstate = animationwrapper.animstate.STATE_WAITINGASSET;
mAnimationsWrapper[mAnimID].mPotentialyDead = false;
}
else
{
// Its a new animation do the decode dance
animationwrapper aw = new animationwrapper(mAnimID);
mAnimationsWrapper.Add(mAnimID, aw);
}
}
public void resetbonescales()
{
foreach (var src in mBones)
{
src.Value.scale = Vector3.One;
src.Value.offset_pos = Vector3.Zero;
}
}
public void deformbone(string name, Vector3 pos, Quaternion rotation)
{
Bone bone;
if (mBones.TryGetValue(name, out bone))
{
bone.deformbone(pos, rotation);
}
}
public void scalebone(string name, Vector3 scale)
{
Bone bone;
if (mBones.TryGetValue(name, out bone))
{
// Logger.Log(String.Format("scalebone() {0} {1}", name, scale.ToString()),Helpers.LogLevel.Info);
bone.scalebone(scale);
}
}
public void offsetbone(string name, Vector3 offset)
{
Bone bone;
if (mBones.TryGetValue(name, out bone))
{
bone.offsetbone(offset);
}
}
//TODO check offset and rot calcuations should each offset be multiplied by its parent rotation in
// a standard child/parent rot/offset way?
public Vector3 getOffset(string bonename)
{
Bone b;
return mBones.TryGetValue(bonename, out b) ? (b.getTotalOffset()) : Vector3.Zero;
}
public Quaternion getRotation(string bonename)
{
Bone b;
return mBones.TryGetValue(bonename, out b) ? (b.getTotalRotation()) : Quaternion.Identity;
}
public void flushanimations()
{
lock (mAnimationsWrapper)
{
List<UUID> kills = new List<UUID>();
foreach (animationwrapper ar in mAnimationsWrapper.Values)
{
if (ar.playstate == animationwrapper.animstate.STATE_STOP)
{
kills.Add(ar.mAnimation);
}
ar.mPotentialyDead = true;
}
foreach (UUID key in kills)
{
mAnimationsWrapper.Remove(key);
//Logger.Log(string.Format("Removing dead animation {0} from av", key), Helpers.LogLevel.Info);
}
}
}
public void flushanimationsfinal()
{
lock (mAnimationsWrapper)
{
foreach (animationwrapper ar in mAnimationsWrapper.Values)
{
if (ar.mPotentialyDead)
{
// Logger.Log(string.Format("Animation {0} is being marked for easeout (dead)",ar.mAnimation.ToString()),Helpers.LogLevel.Info);
// Should we just stop dead? i think not it may get jerky
ar.playstate = animationwrapper.animstate.STATE_EASEOUT;
ar.mRunTime = 0; //fix me nasty hack
}
}
}
}
// Add animations to the global decoded list
// TODO garbage collect unused animations somehow
public static void addanimation(OpenMetaverse.Assets.Asset asset, UUID tid, BinBVHAnimationReader b, UUID animKey)
{
RenderAvatar av;
mAnimationTransactions.TryGetValue(tid, out av);
if (av == null)
return;
mAnimationTransactions.Remove(tid);
if (asset != null)
{
b = new BinBVHAnimationReader(asset.AssetData);
mAnimationCache[asset.AssetID] = b;
Logger.Log("Adding new decoded animaton known animations " + asset.AssetID, Helpers.LogLevel.Info);
}
if (!av.glavatar.skel.mAnimationsWrapper.ContainsKey(animKey))
{
Logger.Log($"Animation {animKey} is not in mAnimationsWrapper! ", Helpers.LogLevel.Warning);
return;
}
// This sets the anim in the wrapper class;
av.glavatar.skel.mAnimationsWrapper[animKey].anim = b;
int pos = 0;
foreach (binBVHJoint joint in b.joints)
{
binBVHJointState state;
state.lastkeyframe_rot = 0;
state.nextkeyframe_rot = 1;
state.lastkeyframe_pos = 0;
state.nextkeyframe_pos = 1;
state.currenttime_rot = 0;
state.currenttime_pos = 0;
state.pos_loopinframe = 0;
state.pos_loopoutframe = joint.positionkeys.Length - 1;
state.rot_loopinframe = 0;
state.rot_loopoutframe = joint.rotationkeys.Length - 1;
state.easeoutfactor = 1.0f;
state.easeoutrot = Quaternion.Identity;
if (b.Loop)
{
int frame = 0;
foreach (binBVHJointKey key in joint.rotationkeys)
{
if (key.time == b.InPoint)
{
state.rot_loopinframe = frame;
}
if (key.time == b.OutPoint)
{
state.rot_loopoutframe = frame;
}
frame++;
}
frame = 0;
foreach (binBVHJointKey key in joint.positionkeys)
{
if (key.time == b.InPoint)
{
state.pos_loopinframe = frame;
}
if (key.time == b.OutPoint)
{
state.pos_loopoutframe = frame;
}
frame++;
}
}
b.joints[pos].Tag = state;
pos++;
}
av.glavatar.skel.mAnimationsWrapper[animKey].playstate = animationwrapper.animstate.STATE_EASEIN;
recalcpriorities(av);
}
public static void recalcpriorities(RenderAvatar av)
{
lock (av.glavatar.skel.mAnimationsWrapper)
{
//av.glavatar.skel.mAnimationsWrapper.Add(new animationwrapper(b));
//pre calculate all joint priorities here
av.glavatar.skel.mPriority.Clear();
foreach (var kvp in av.glavatar.skel.mAnimationsWrapper)
{
int jpos = 0;
animationwrapper ar = kvp.Value;
if (ar.anim == null)
continue;
foreach (binBVHJoint joint in ar.anim.joints)
{
if (ar.anim == null)
continue;
//warning struct copy non reference
binBVHJointState state = (binBVHJointState)ar.anim.joints[jpos].Tag;
if (ar.playstate == animationwrapper.animstate.STATE_STOP || ar.playstate == animationwrapper.animstate.STATE_EASEOUT)
continue;
//FIX ME need to consider ease out here on priorities somehow
int prio = 0;
//Quick hack to stack animations in the correct order
//TODO we need to do this per joint as they all have their own priorities as well ;-(
if (av.glavatar.skel.mPriority.TryGetValue(joint.Name, out prio))
{
if (prio > (ar.anim.Priority))
continue;
}
av.glavatar.skel.mPriority[joint.Name] = ar.anim.Priority;
jpos++;
//av.glavatar.skel.mAnimationsWrapper[kvp.Key].playstate = animationwrapper.animstate.STATE_EASEIN;
}
}
}
}
public void animate(float lastframetime)
{
lock (mAnimationsWrapper)
{
jointdeforms.Clear();
foreach (animationwrapper ar in mAnimationsWrapper.Values)
{
if (ar.playstate == animationwrapper.animstate.STATE_WAITINGASSET)
continue;
if (ar.anim == null)
continue;
ar.mRunTime += lastframetime;
// EASE FACTORS
// Caclulate ease factors now they are common to all joints in a given animation
float factor = 1.0f;
if (ar.playstate == animationwrapper.animstate.STATE_EASEIN)
{
if (ar.mRunTime >= ar.anim.EaseInTime)
{
ar.playstate = animationwrapper.animstate.STATE_PLAY;
//Console.WriteLine(String.Format("{0} Now in STATE_PLAY", ar.mAnimation));
}
else
{
factor = 1.0f - ((ar.anim.EaseInTime - ar.mRunTime) / ar.anim.EaseInTime);
}
//Console.WriteLine(String.Format("EASE IN {0} {1}",factor.ToString(),ar.mAnimation));
}
if (ar.playstate == animationwrapper.animstate.STATE_EASEOUT)
{
if (ar.mRunTime >= ar.anim.EaseOutTime)
{
ar.stopanim();
factor = 0;
//Console.WriteLine(String.Format("{0} Now in STATE_STOP", ar.mAnimation));
}
else
{
factor = 1.0f - (ar.mRunTime / ar.anim.EaseOutTime);
}
//Console.WriteLine(String.Format("EASE OUT {0} {1}", factor.ToString(), ar.mAnimation));
}
// we should not need this, this implies bad math above
factor = Utils.Clamp(factor, 0.0f, 1.0f);
//factor = 1.0f;
//END EASE FACTORS
int jpos = 0;
foreach (binBVHJoint joint in ar.anim.joints)
{
bool easeoutset = false;
//warning struct copy non reference
binBVHJointState state = (binBVHJointState)ar.anim.joints[jpos].Tag;
if (ar.playstate == animationwrapper.animstate.STATE_STOP)
continue;
int prio = 0;
//Quick hack to stack animations in the correct order
//TODO we need to do this per joint as they all have their own priorities as well ;-(
if (mPriority.TryGetValue(joint.Name, out prio))
{
//if (prio > (ar.anim.Priority))
//continue;
}
Vector3 poslerp = Vector3.Zero;
Quaternion rotlerp = Quaternion.Identity;
// Position
if (ar.anim.joints[jpos].positionkeys.Length >= 2 && joint.Name == "mPelvis")
{
//Console.WriteLine("Animate time " + state.currenttime_pos.ToString());
state.currenttime_pos += lastframetime;
float currentime = state.currenttime_pos;
bool overrun = false;
if (state.currenttime_pos > ar.anim.OutPoint)
{
//overrun state
int itterations = (int)(state.currenttime_pos / ar.anim.OutPoint) + 1;
state.currenttime_pos = currentime = ar.anim.InPoint + ((ar.anim.OutPoint - ar.anim.InPoint) - (((ar.anim.OutPoint - ar.anim.InPoint) * itterations) - state.currenttime_pos));
overrun = true;
}
binBVHJointKey pos_next = ar.anim.joints[jpos].positionkeys[state.nextkeyframe_pos];
binBVHJointKey pos_last = ar.anim.joints[jpos].positionkeys[state.lastkeyframe_pos];
// if the current time > than next key frame time we move keyframes
if (currentime >= pos_next.time || overrun)
{
//Console.WriteLine("bump");
state.lastkeyframe_pos++;
state.nextkeyframe_pos++;
if (ar.anim.Loop)
{
if (state.nextkeyframe_pos > state.pos_loopoutframe)
state.nextkeyframe_pos = state.pos_loopinframe;
if (state.lastkeyframe_pos > state.pos_loopoutframe)
state.lastkeyframe_pos = state.pos_loopinframe;
if (state.nextkeyframe_pos >= ar.anim.joints[jpos].positionkeys.Length)
state.nextkeyframe_pos = state.pos_loopinframe;
if (state.lastkeyframe_pos >= ar.anim.joints[jpos].positionkeys.Length)
state.lastkeyframe_pos = state.pos_loopinframe;
}
else
{
if (state.nextkeyframe_pos >= ar.anim.joints[jpos].positionkeys.Length)
state.nextkeyframe_pos = ar.anim.joints[jpos].positionkeys.Length - 1;
if (state.lastkeyframe_pos >= ar.anim.joints[jpos].positionkeys.Length)
{
state.lastkeyframe_pos = ar.anim.joints[jpos].positionkeys.Length - 1;
ar.playstate = animationwrapper.animstate.STATE_EASEOUT;
//animation over
}
}
}
//if (pos_next.time == pos_last.time)
//{
// ar.playstate = animationwrapper.animstate.STATE_EASEOUT;
//}
// update the pointers incase they have been moved
pos_next = ar.anim.joints[jpos].positionkeys[state.nextkeyframe_pos];
pos_last = ar.anim.joints[jpos].positionkeys[state.lastkeyframe_pos];
// TODO the lerp/delta is faulty
// it is not going to handle loop points when we wrap around as last will be > next
// it also fails when currenttime < last_time which occurs as keyframe[0] is not exactly at
// t(0).
float delta = (state.currenttime_pos - pos_last.time) / (pos_next.time - pos_last.time);
delta = Utils.Clamp(delta, 0f, 1f);
poslerp = Vector3.Lerp(pos_last.key_element, pos_next.key_element, delta) * factor;
//Console.WriteLine(string.Format("Time {0} {1} {2} {3} {4}", state.currenttime_pos, delta, poslerp.ToString(), state.lastkeyframe_pos, state.nextkeyframe_pos));
}
// end of position
//rotation
if (ar.anim.joints[jpos].rotationkeys.Length >= 2)
{
state.currenttime_rot += lastframetime;
float currentime = state.currenttime_rot;
bool overrun = false;
if (state.currenttime_rot > ar.anim.OutPoint)
{
//overrun state
int itterations = (int)(state.currenttime_rot / ar.anim.OutPoint) + 1;
state.currenttime_rot = currentime = ar.anim.InPoint + ((ar.anim.OutPoint - ar.anim.InPoint) - (((ar.anim.OutPoint - ar.anim.InPoint) * itterations) - state.currenttime_rot));
overrun = true;
}
binBVHJointKey rot_next = ar.anim.joints[jpos].rotationkeys[state.nextkeyframe_rot];
binBVHJointKey rot_last = ar.anim.joints[jpos].rotationkeys[state.lastkeyframe_rot];
// if the current time > than next key frame time we move keyframes
if (currentime >= rot_next.time || overrun)
{
state.lastkeyframe_rot++;
state.nextkeyframe_rot++;
if (ar.anim.Loop)
{
if (state.nextkeyframe_rot > state.rot_loopoutframe)
state.nextkeyframe_rot = state.rot_loopinframe;
if (state.lastkeyframe_rot > state.rot_loopoutframe)
state.lastkeyframe_rot = state.rot_loopinframe;
}
else
{
if (state.nextkeyframe_rot >= ar.anim.joints[jpos].rotationkeys.Length)
state.nextkeyframe_rot = ar.anim.joints[jpos].rotationkeys.Length - 1;
if (state.lastkeyframe_rot >= ar.anim.joints[jpos].rotationkeys.Length)
{
state.lastkeyframe_rot = ar.anim.joints[jpos].rotationkeys.Length - 1;
ar.playstate = animationwrapper.animstate.STATE_EASEOUT;
//animation over
}
}
}
// update the pointers incase they have been moved
rot_next = ar.anim.joints[jpos].rotationkeys[state.nextkeyframe_rot];
rot_last = ar.anim.joints[jpos].rotationkeys[state.lastkeyframe_rot];
// TODO the lerp/delta is faulty
// it is not going to handle loop points when we wrap around as last will be > next
// it also fails when currenttime < last_time which occurs as keyframe[0] is not exactly at
// t(0).
float delta = state.currenttime_rot - rot_last.time / (rot_next.time - rot_last.time);
delta = Utils.Clamp(delta, 0f, 1f);
Vector3 rotlerpv = Vector3.Lerp(rot_last.key_element, rot_next.key_element, delta);
// rotlerp = Quaternion.Slerp(Quaternion.Identity,new Quaternion(rotlerpv.X, rotlerpv.Y, rotlerpv.Z), factor);
if (easeoutset == false && ar.playstate == animationwrapper.animstate.STATE_EASEOUT)
{
easeoutset = true;
state.easeoutrot = new Quaternion(rotlerpv.X, rotlerpv.Y, rotlerpv.Z);
state.easeoutfactor = factor;
}
else
{
rotlerp = new Quaternion(rotlerpv.X, rotlerpv.Y, rotlerpv.Z);
}
}
//end of rotation
joint jointstate = new joint();
if (jointdeforms.TryGetValue(ar.anim.joints[jpos].Name, out jointstate))
{
jointstate.offset += (poslerp);
if (ar.playstate != animationwrapper.animstate.STATE_EASEOUT)
{
jointstate.rotation = easeoutset
? Quaternion.Slerp(jointstate.rotation, state.easeoutrot, state.easeoutfactor)
: rotlerp;
}
//jointstate.rotation= Quaternion.Slerp(jointstate.rotation, rotlerp, 0.5f);
}
else
{
jointstate = new joint
{
rotation = rotlerp,
offset = poslerp
};
jointdeforms.Add(ar.anim.joints[jpos].Name, jointstate);
}
//warning struct copy non reference
ar.anim.joints[jpos].Tag = state;
jpos++;
}
}
}
foreach (var kvp in jointdeforms)
{
deformbone(kvp.Key, kvp.Value.offset, kvp.Value.rotation);
}
mNeedsMeshRebuild = true;
}
}
public class Bone
{
public string name;
public Vector3 pos;
public Quaternion rot;
public Vector3 scale;
public Vector3 piviot;
public Vector3 offset_pos;
public Vector3 orig_pos;
public Quaternion orig_rot;
public Vector3 orig_scale;
public Vector3 orig_piviot;
Matrix4 mDeformMatrix = Matrix4.Identity;
public Vector3 animation_offset;
public Bone parent;
public List<Bone> children = new List<Bone>();
public static Dictionary<string, Bone> mBones = new Dictionary<string, Bone>();
public static Dictionary<int, Bone> mIndexedBones = new Dictionary<int, Bone>();
static int boneaddindex = 0;
private bool rotdirty = true;
private bool posdirty = true;
//Inverse bind matrix with bone scale
private Vector3 mTotalPos;
private Quaternion mTotalRot;
private Vector3 mDeltaPos;
public string mParentBone = null;
public Bone()
{
}
public Bone(Bone source)
{
name = source.name;
pos = new Vector3(source.pos);
rot = new Quaternion(source.rot);
scale = new Vector3(source.scale);
piviot = new Vector3(source.piviot);
offset_pos = new Vector3(source.offset_pos);
orig_piviot = source.orig_piviot;
orig_pos = source.orig_pos;
orig_rot = source.orig_rot;
orig_scale = source.orig_scale;
mParentBone = source.mParentBone;
mDeformMatrix = new Matrix4(source.mDeformMatrix);
}
public static void loadbones(string skeletonfilename)
{
lock (mBones) mBones.Clear();
string basedir = Path.Combine(Directory.GetCurrentDirectory(), "character");
XmlDocument skeleton = new XmlDocument();
skeleton.Load(Path.Combine(basedir, skeletonfilename));
XmlNode boneslist = skeleton.GetElementsByTagName("linden_skeleton")[0];
addbone(boneslist.ChildNodes[0], null);
}
public static void addbone(XmlNode bone, Bone parent)
{
if (bone.Name != "bone")
return;
Bone b = new Bone {name = bone.Attributes.GetNamedItem("name").Value};
string pos = bone.Attributes.GetNamedItem("pos").Value;
string[] posparts = pos.Split(' ');
b.pos = new Vector3(float.Parse(posparts[0], Utils.EnUsCulture), float.Parse(posparts[1], Utils.EnUsCulture), float.Parse(posparts[2], Utils.EnUsCulture));
b.orig_pos = new Vector3(b.pos);
b.offset_pos = new Vector3(b.pos);
string rot = bone.Attributes.GetNamedItem("rot").Value;
string[] rotparts = rot.Split(' ');
b.rot = Quaternion.CreateFromEulers((float)(float.Parse(rotparts[0], Utils.EnUsCulture) * Math.PI / 180f), (float)(float.Parse(rotparts[1], Utils.EnUsCulture) * Math.PI / 180f), (float)(float.Parse(rotparts[2], Utils.EnUsCulture) * Math.PI / 180f));
b.orig_rot = new Quaternion(b.rot);
string scale = bone.Attributes.GetNamedItem("scale").Value;
string[] scaleparts = scale.Split(' ');
b.scale = new Vector3(float.Parse(scaleparts[0], Utils.EnUsCulture), float.Parse(scaleparts[1], Utils.EnUsCulture), float.Parse(scaleparts[2], Utils.EnUsCulture));
b.orig_scale = new Vector3(b.scale);
float[] deform = Math3D.CreateSRTMatrix(new Vector3(1, 1, 1), b.rot, b.orig_pos);
b.mDeformMatrix = new Matrix4(deform[0], deform[1], deform[2], deform[3], deform[4], deform[5], deform[6], deform[7], deform[8], deform[9], deform[10], deform[11], deform[12], deform[13], deform[14], deform[15]);
//TODO piviot
b.parent = parent;
if (parent != null)
{
b.mParentBone = parent.name;
parent.children.Add(b);
}
lock (mBones) mBones.Add(b.name, b);
mIndexedBones.Add(boneaddindex++, b);
Logger.Log("Found bone " + b.name, Helpers.LogLevel.Info);
foreach (XmlNode childbone in bone.ChildNodes)
{
addbone(childbone, b);
}
}
public void deformbone(Vector3 dpos, Quaternion rot)
{
//float[] deform = Math3D.CreateSRTMatrix(scale, rot, this.orig_pos);
//mDeformMatrix = new Matrix4(deform[0], deform[1], deform[2], deform[3], deform[4], deform[5], deform[6], deform[7], deform[8], deform[9], deform[10], deform[11], deform[12], deform[13], deform[14], deform[15]);
//this.offset_pos += pos;
//this.pos = pos;
// this.offset_pos = pos;
animation_offset = dpos;
//this.pos = Bone.mBones[name].offset_pos + dpos;
lock (mBones) this.rot = mBones[name].orig_rot * rot;
markdirty();
}
public void scalebone(Vector3 scale)
{
this.scale *= scale;
markdirty();
}
public void offsetbone(Vector3 offset)
{
offset_pos += offset;
markdirty();
}
// If we deform a bone mark this bone and all its children as dirty.
public void markdirty()
{
rotdirty = true;
posdirty = true;
foreach (Bone childbone in children)
{
childbone.markdirty();
}
}
public Matrix4 getdeform()
{
if (parent != null)
{
return mDeformMatrix * parent.getdeform();
}
else
{
return mDeformMatrix;
}
}
private Vector3 getOffset()
{
if (parent != null)
{
Quaternion totalrot = getParentRot(); // we don't want this joints rotation included
Vector3 parento = parent.getOffset();
mTotalPos = parento + pos * parent.scale * totalrot;
Vector3 orig = getOrigOffset();
mDeltaPos = mTotalPos - orig;
posdirty = false;
return mTotalPos;
}
else
{
Vector3 orig = getOrigOffset();
//mTotalPos = (pos * scale)+offset_pos;
//mTotalPos = (pos) + offset_pos;
mTotalPos = pos;
mDeltaPos = mTotalPos - orig;
posdirty = false;
return mTotalPos;
}
}
public Vector3 getMyOffset()
{
return pos * scale;
}
// Try to save some cycles by not recalculating positions and rotations every time
public Vector3 getTotalOffset()
{
return posdirty == false ? mTotalPos : getOffset();
}
public Vector3 getDeltaOffset()
{
if (posdirty == false)
{
return mDeltaPos;
}
getOffset();
return mDeltaPos;
}
private Vector3 getOrigOffset()
{
if (parent != null)
{
return (parent.getOrigOffset() + orig_pos);
}
else
{
return orig_pos;
}
}
private static Quaternion getRotation(string bonename)
{
Bone b;
lock (mBones)
{
return mBones.TryGetValue(bonename, out b) ? (b.getRotation()) : Quaternion.Identity;
}
}
private Quaternion getParentRot()
{
Quaternion totalrot = Quaternion.Identity;
if (parent != null)
{
totalrot = parent.getRotation();
}
return totalrot;
}
private Quaternion getRotation()
{
Quaternion totalrot = rot;
if (parent != null)
{
totalrot = parent.getRotation() * rot;
}
mTotalRot = totalrot;
rotdirty = false;
return totalrot;
}
public Quaternion getTotalRotation()
{
return rotdirty == false ? mTotalRot : getRotation();
}
}
public class BoneDeform
{
public BoneDeform(Vector3 scale, Vector3 offset)
{
this.scale = scale;
this.offset = offset;
}
public Vector3 scale;
public Vector3 offset;
}
public class VisualParamEx
{
//All visual params indexed by ID
public static Dictionary<int, VisualParamEx> AllParams = new Dictionary<int, VisualParamEx>();
// The sorted list of tweakable params, this matches the AvatarAppearance packet visual
// parameters ordering
public static SortedList TweakableParams = new SortedList();
public Dictionary<string, BoneDeform> BoneDeforms = null;
public Dictionary<string, VolumeDeform> VolumeDeforms = null;
public List<driven> ChildParams = null;
public List<VisualParamEx> IdenticalIds = new List<VisualParamEx>();
public string MorphMesh = null;
enum GroupType
{
VISUAL_PARAM_GROUP_TWEAKABLE = 0,
VISUAL_PARAM_GROUP_ANIMATABLE,
VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT,
VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE
}
public struct VolumeDeform
{
public string name;
public Vector3 scale;
public Vector3 pos;
}
public enum EparamSex
{
SEX_MALE = 0x1,
SEX_FEMALE = 0x2,
SEX_BOTH = 0x3
}
public enum ParamType
{
TYPE_NOTSET = 0,
TYPE_BONEDEFORM,
TYPE_MORPH,
TYPE_DRIVER,
TYPE_COLOR,
TYPE_LAYER
}
public struct driven
{
public int id;
public float max1;
public float max2;
public float min1;
public float min2;
public bool hasMinMax;
}
public string meshname;
/// <summary>Index of this visual param</summary>
public int ParamID;
/// <summary>Internal name</summary>
public string Name;
/// <summary>Group ID this parameter belongs to</summary>
public int Group;
/// <summary>Name of the wearable this parameter belongs to</summary>
public string Wearable;
/// <summary>Displayable label of this characteristic</summary>
public string Label;
/// <summary>Displayable label for the minimum value of this characteristic</summary>
public string LabelMin;
/// <summary>Displayable label for the maximum value of this characteristic</summary>
public string LabelMax;
/// <summary>Default value</summary>
public float DefaultValue;
/// <summary>Minimum value</summary>
public float MinValue;
/// <summary>Maximum value</summary>
public float MaxValue;
/// <summary>Is this param used for creation of bump layer?</summary>
public bool IsBumpAttribute;
/// <summary>Alpha blending/bump info</summary>
public VisualAlphaParam? AlphaParams;
/// <summary>Color information</summary>
public VisualColorParam? ColorParams;
/// <summary>Array of param IDs that are drivers for this parameter</summary>
public int[] Drivers;
/// <summary>The Avatar Sex that this parameter applies to</summary>
public EparamSex sex;
public ParamType pType;
public static int count = 0;
/// <summary>
/// Set all the values through the constructor
/// </summary>
/// <param name="paramID">Index of this visual param</param>
/// <param name="name">Internal name</param>
/// <param name="group"></param>
/// <param name="wearable"></param>
/// <param name="label">Displayable label of this characteristic</param>
/// <param name="labelMin">Displayable label for the minimum value of this characteristic</param>
/// <param name="labelMax">Displayable label for the maximum value of this characteristic</param>
/// <param name="def">Default value</param>
/// <param name="min">Minimum value</param>
/// <param name="max">Maximum value</param>
/// <param name="isBumpAttribute">Is this param used for creation of bump layer?</param>
/// <param name="drivers">Array of param IDs that are drivers for this parameter</param>
/// <param name="alpha">Alpha blending/bump info</param>
/// <param name="colorParams">Color information</param>
public VisualParamEx(int paramID, string name, int group, string wearable, string label, string labelMin, string labelMax,
float def, float min, float max, bool isBumpAttribute, int[] drivers, VisualAlphaParam? alpha, VisualColorParam? colorParams)
{
ParamID = paramID;
Name = name;
Group = group;
Wearable = wearable;
Label = label;
LabelMin = labelMin;
LabelMax = labelMax;
DefaultValue = def;
MaxValue = max;
MinValue = min;
IsBumpAttribute = isBumpAttribute;
Drivers = drivers;
AlphaParams = alpha;
ColorParams = colorParams;
sex = EparamSex.SEX_BOTH;
}
public bool matchchildnode(string test, XmlNode node)
{
return node.ChildNodes.Cast<XmlNode>().Any(n => n.Name == test);
}
public VisualParamEx(XmlNode node)
{
ParamID = Int32.Parse(node.Attributes.GetNamedItem("id").Value);
Name = node.Attributes.GetNamedItem("name").Value;
Group = Int32.Parse(node.Attributes.GetNamedItem("group").Value);
//These dont exist for facal expresion morphs
if (node.Attributes.GetNamedItem("wearable") != null)
Wearable = node.Attributes.GetNamedItem("wearable").Value;
MinValue = float.Parse(node.Attributes.GetNamedItem("value_min").Value, Utils.EnUsCulture);
MaxValue = float.Parse(node.Attributes.GetNamedItem("value_max").Value, Utils.EnUsCulture);
// These do not exists for driven parameters
if (node.Attributes.GetNamedItem("label_min") != null)
{
LabelMin = node.Attributes.GetNamedItem("label_min").Value;
}
if (node.Attributes.GetNamedItem("label_max") != null)
{
LabelMax = node.Attributes.GetNamedItem("label_max").Value;
}
XmlNode sexnode = node.Attributes.GetNamedItem("sex");
if (sexnode != null)
{
switch (sexnode.Value)
{
case "male":
sex = EparamSex.SEX_MALE;
break;
case "female":
sex = EparamSex.SEX_FEMALE;
break;
default:
sex = EparamSex.SEX_BOTH;
break;
}
}
if (node.ParentNode.Name == "mesh")
{
MorphMesh = node.ParentNode.Attributes.GetNamedItem("type").Value;
}
if (Group == (int)GroupType.VISUAL_PARAM_GROUP_TWEAKABLE)
{
if (!TweakableParams.ContainsKey(ParamID)) //stupid duplicate shared params
{
TweakableParams.Add(ParamID, this);
}
else
{
Logger.Log($"Warning duplicate tweakable parameter ID {count} {Name}", Helpers.LogLevel.Warning);
}
count++;
}
if (AllParams.ContainsKey(ParamID))
{
//Logger.Log("Shared VisualParam id " + ParamID.ToString() + " "+Name, Helpers.LogLevel.Info);
AllParams[ParamID].IdenticalIds.Add(this);
}
else
{
//Logger.Log("VisualParam id " + ParamID.ToString() + " " + Name, Helpers.LogLevel.Info);
AllParams.Add(ParamID, this);
}
if (matchchildnode("param_skeleton", node))
{
pType = ParamType.TYPE_BONEDEFORM;
// If we are in the skeleton section then we also have bone deforms to parse
BoneDeforms = new Dictionary<string, BoneDeform>();
if (node.HasChildNodes && node.ChildNodes[0].HasChildNodes)
{
ParseBoneDeforms(node.ChildNodes[0].ChildNodes);
}
}
if (matchchildnode("param_morph", node))
{
pType = ParamType.TYPE_MORPH;
VolumeDeforms = new Dictionary<string, VolumeDeform>();
if (node.HasChildNodes && node.ChildNodes[0].HasChildNodes)
{
ParseVolumeDeforms(node.ChildNodes[0].ChildNodes);
}
}
if (matchchildnode("param_driver", node))
{
pType = ParamType.TYPE_DRIVER;
ChildParams = new List<driven>();
if (node.HasChildNodes && node.ChildNodes[0].HasChildNodes) //LAZY
{
ParseDrivers(node.ChildNodes[0].ChildNodes);
}
}
if (matchchildnode("param_color", node))
{
pType = ParamType.TYPE_COLOR;
if (node.HasChildNodes)
{
foreach (XmlNode colorchild in node.ChildNodes)
{
if (colorchild.Name == "param_color")
{
//TODO extract <value color="50, 25, 5, 255" />
}
}
}
}
}
void ParseBoneDeforms(XmlNodeList deforms)
{
foreach (XmlNode node in deforms)
{
if (node.Name == "bone")
{
string name = node.Attributes.GetNamedItem("name").Value;
Vector3 scale = Vector3.One;
Vector3 offset = Vector3.One;
if (node.Attributes.GetNamedItem("scale") != null)
scale = XmlParseVector(node.Attributes.GetNamedItem("scale").Value);
if (node.Attributes.GetNamedItem("offset") != null)
offset = XmlParseVector(node.Attributes.GetNamedItem("offset").Value);
BoneDeform bd = new BoneDeform(scale, offset);
BoneDeforms.Add(name, bd);
}
}
}
void ParseVolumeDeforms(XmlNodeList deforms)
{
foreach (XmlNode node in deforms)
{
if (node.Name == "volume_morph")
{
VolumeDeform vd = new VolumeDeform
{
name = node.Attributes.GetNamedItem("name").Value.ToLower(),
scale =
node.Attributes.GetNamedItem("scale") != null
? XmlParseVector(node.Attributes.GetNamedItem("scale").Value)
: new Vector3(0, 0, 0),
pos =
node.Attributes.GetNamedItem("pos") != null
? XmlParseVector(node.Attributes.GetNamedItem("pos").Value)
: new Vector3(0f, 0f, 0f)
};
VolumeDeforms.Add(vd.name, vd);
}
}
}
void ParseDrivers(XmlNodeList drivennodes)
{
foreach (XmlNode node in drivennodes)
{
if (node.Name == "driven")
{
driven d = new driven {id = Int32.Parse(node.Attributes.GetNamedItem("id").Value)};
XmlNode param = node.Attributes.GetNamedItem("max1");
if (param != null)
{
d.max1 = float.Parse(param.Value, Utils.EnUsCulture);
d.max2 = float.Parse(node.Attributes.GetNamedItem("max2").Value, Utils.EnUsCulture);
d.min1 = float.Parse(node.Attributes.GetNamedItem("min1").Value, Utils.EnUsCulture);
d.min2 = float.Parse(node.Attributes.GetNamedItem("min2").Value, Utils.EnUsCulture);
d.hasMinMax = true;
}
else
{
d.hasMinMax = false;
}
ChildParams.Add(d);
}
}
}
public static Vector3 XmlParseVector(string data)
{
string[] posparts = data.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
return new Vector3(float.Parse(posparts[0], Utils.EnUsCulture), float.Parse(posparts[1], Utils.EnUsCulture), float.Parse(posparts[2], Utils.EnUsCulture));
}
public static Quaternion XmlParseRotation(string data)
{
string[] rotparts = data.Split(' ');
return Quaternion.CreateFromEulers((float)(float.Parse(rotparts[0]) * Math.PI / 180f), (float)(float.Parse(rotparts[1]) * Math.PI / 180f), (float)(float.Parse(rotparts[2]) * Math.PI / 180f));
}
}
public class RenderAvatar : SceneObject
{
public static Dictionary<int, string> BakedTextures = new Dictionary<int, string>
{
{ 8, "head" },
{ 9, "upper" },
{ 10, "lower" },
{ 11, "eyes" },
{ 19, "skirt" },
{ 20, "hair" }
};
public GLAvatar glavatar = new GLAvatar();
public Avatar avatar;
public FaceData[] data = new FaceData[32];
public Dictionary<UUID, Animation> animlist = new Dictionary<UUID, Animation>();
public Dictionary<WearableType, AppearanceManager.WearableData> Wearables = new Dictionary<WearableType, AppearanceManager.WearableData>();
public static readonly BoundingVolume AvatarBoundingVolume;
// Static constructor
static RenderAvatar()
{
AvatarBoundingVolume = new BoundingVolume();
AvatarBoundingVolume.FromScale(Vector3.One);
}
// Default constructor
public RenderAvatar()
{
BoundingVolume = AvatarBoundingVolume;
Type = SceneObjectType.Avatar;
}
public Primitive BasePrim
{
get => avatar;
set
{
if (value is Avatar)
{
avatar = (Avatar)value;
AvatarBoundingVolume.CalcScaled(avatar.Scale);
}
}
}
public float Height;
public float PelvisToFoot;
public void UpdateSize()
{
float F_SQRT2 = 1.4142135623730950488016887242097f;
Vector3 pelvis_scale = glavatar.skel.mBones["mPelvis"].scale;
Vector3 skull = glavatar.skel.mBones["mSkull"].pos;
Vector3 skull_scale = glavatar.skel.mBones["mSkull"].scale;
Vector3 neck = glavatar.skel.mBones["mNeck"].pos;
Vector3 neck_scale = glavatar.skel.mBones["mNeck"].scale;
Vector3 chest = glavatar.skel.mBones["mChest"].pos;
Vector3 chest_scale = glavatar.skel.mBones["mChest"].scale;
Vector3 head = glavatar.skel.mBones["mHead"].pos;
Vector3 head_scale = glavatar.skel.mBones["mHead"].scale;
Vector3 torso = glavatar.skel.mBones["mTorso"].pos;
Vector3 torso_scale = glavatar.skel.mBones["mTorso"].scale;
Vector3 hip = glavatar.skel.mBones["mHipLeft"].pos;
Vector3 hip_scale = glavatar.skel.mBones["mHipLeft"].scale;
Vector3 knee = glavatar.skel.mBones["mKneeLeft"].pos;
Vector3 knee_scale = glavatar.skel.mBones["mKneeLeft"].scale;
Vector3 ankle = glavatar.skel.mBones["mAnkleLeft"].pos;
Vector3 ankle_scale = glavatar.skel.mBones["mAnkleLeft"].scale;
Vector3 foot = glavatar.skel.mBones["mFootLeft"].pos;
// mAvatarOffset.Z = getVisualParamWeight(11001);
float mPelvisToFoot = hip.Z * pelvis_scale.Z -
knee.Z * hip_scale.Z -
ankle.Z * knee_scale.Z -
foot.Z * ankle_scale.Z;
Vector3 new_body_size;
new_body_size.Z = mPelvisToFoot +
// the sqrt(2) correction below is an approximate
// correction to get to the top of the head
F_SQRT2 * (skull.Z * head_scale.Z) +
head.Z * neck_scale.Z +
neck.Z * chest_scale.Z +
chest.Z * torso_scale.Z +
torso.Z * pelvis_scale.Z;
Height = new_body_size.Z;
PelvisToFoot = mPelvisToFoot;
}
public Vector3 AdjustedPosition(Vector3 source)
{
return new Vector3(source.X, source.Y, source.Z - Height + PelvisToFoot);
}
}
}