public void Populate(TiltBrush.GeometryPool pool) { positionAccessor.Populate(pool.m_Vertices, flipY: false, calculateMinMax: true); if (normalAccessor != null) { normalAccessor.Populate(pool.m_Normals, flipY: false, calculateMinMax: false); } if (colorAccessor != null) { colorAccessor.Populate(pool.m_Colors); } if (tangentAccessor != null) { tangentAccessor.Populate(pool.m_Tangents, flipY: false, calculateMinMax: false); } // UVs may be 1, 2, 3 or 4 element tuples, which the following helper method resolves. // In the case of zero UVs, the texCoord accessor will be null and will not be populated. Debug.Assert(TiltBrush.GeometryPool.kNumTexcoords == 3); Debug.Assert(texCoord3Accessor == null); var layout = pool.Layout; PopulateUv(0, pool, texCoord0Accessor, layout.texcoord0.semantic); PopulateUv(1, pool, texCoord1Accessor, layout.texcoord1.semantic); PopulateUv(2, pool, texCoord2Accessor, layout.texcoord2.semantic); PopulateUv(3, pool, texCoord3Accessor, Semantic.Unspecified); }
public void Populate(Transform m, ref GlTF_Accessor invBindMatricesAccessor, int invBindAccessorIndex) { SkinnedMeshRenderer skinMesh = m.GetComponent <SkinnedMeshRenderer>(); if (!skinMesh) { return; } // Populate bind poses. From https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html: // The bind pose is bone's inverse transformation matrix // In this case we also make this matrix relative to the root // So that we can move the root game object around freely joints = new List <Transform>(); //Collect all bones from skin object. Order should be kept here since bones are referenced in the mesh foreach (Transform t in skinMesh.bones) { joints.Add(t); } Matrix4x4[] invBindMatrices = new Matrix4x4[joints.Count]; for (int i = 0; i < skinMesh.bones.Length; ++i) { // Generates inverseWorldMatrix in right-handed coordinate system Matrix4x4 invBind = skinMesh.sharedMesh.bindposes[i]; convertMatrixLeftToRightHandedness(ref invBind); invBindMatrices[i] = invBind; } invBindMatricesAccessor.Populate(invBindMatrices, m); invBindMatricesAccessorIndex = invBindAccessorIndex; }
public void Populate(Mesh m) { // attributes.Populate (m); if (m.GetTopology(index) == MeshTopology.Triangles) { indices.Populate(m.GetTriangles(index), true); } }
void PopulateTime(string animName, Keyframe[] keyFrames) { timeAccessor = new GlTF_Accessor("accessor_anim_time_" + animName, GlTF_Accessor.Type.SCALAR, GlTF_Accessor.ComponentType.FLOAT); timeAccessor.bufferView = GlTF_Writer.floatBufferView; GlTF_Writer.accessors.Add(timeAccessor); var times = new float[keyFrames.Length]; for (int i = 0; i < keyFrames.Length; i++) { times[i] = keyFrames[i].time; } timeAccessor.Populate(times); }
public void Populate(Mesh m) { positionAccessor.Populate(m.vertices); if (colorAccessor != null) { colorAccessor.Populate(m.colors); } if (normalAccessor != null) { normalAccessor.Populate(m.normals); } if (texCoord0Accessor != null) { texCoord0Accessor.Populate(m.uv, false); } if (texCoord1Accessor != null) { texCoord1Accessor.Populate(m.uv2, false); } if (texCoord2Accessor != null) { texCoord2Accessor.Populate(m.uv3, false); } if (texCoord3Accessor != null) { texCoord3Accessor.Populate(m.uv4, false); } if (lightmapTexCoordAccessor != null) { lightmapTexCoordAccessor.PopulateWithOffsetScale(m.uv2, false); } if (jointAccessor != null) { Vector4[] bones = boneWeightToBoneVec4(m.boneWeights); jointAccessor.Populate(bones); } if (weightAccessor != null) { Vector4[] weights = boneWeightToWeightVec4(m.boneWeights); weightAccessor.Populate(weights); } if (tangentAccessor != null) { tangentAccessor.Populate(m.tangents, false); } }
public void Populate(Mesh m) { positionAccessor.Populate(m.vertices); if (normalAccessor != null) { normalAccessor.Populate(m.normals); } if (texCoord0Accessor != null) { texCoord0Accessor.Populate(m.uv, true); } if (texCoord1Accessor != null) { texCoord1Accessor.Populate(m.uv2, true); } if (texCoord2Accessor != null) { texCoord2Accessor.Populate(m.uv3, true); } if (texCoord3Accessor != null) { texCoord3Accessor.Populate(m.uv4, true); } if (boneIndexAccessor != null) { List <Vector4> indices = new List <Vector4>(); foreach (var bw in m.boneWeights) { indices.Add(new Vector4(bw.boneIndex0, bw.boneIndex1, bw.boneIndex2, bw.boneIndex3)); } boneIndexAccessor.Populate(indices.ToArray()); } if (boneWeightAccessor != null) { List <Vector4> weights = new List <Vector4>(); foreach (var bw in m.boneWeights) { weights.Add(new Vector4(bw.weight0, bw.weight1, bw.weight2, bw.weight3)); } boneWeightAccessor.Populate(weights.ToArray()); } }
public void Populate(Transform m, ref GlTF_Accessor invBindMatricesAccessor, int invBindAccessorIndex) { SkinnedMeshRenderer skinMesh = m.GetComponent <SkinnedMeshRenderer>(); if (!skinMesh) { return; } // Populate bind poses. From https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html: // The bind pose is bone's inverse transformation matrix // In this case we also make this matrix relative to the root // So that we can move the root game object around freely joints = new List <Transform>(); //Collect all bones from skin object. Order should be kept here since bones are referenced in the mesh foreach (Transform t in skinMesh.bones) { joints.Add(t); } // glTF expects a single hierarchy of bones, but Unity skips all the nodes that are not used. // Find the common ancestor of all used bones in order to get a valid bone herarchy rootBone = rebuildBoneHierarchy(skinMesh, ref joints); Matrix4x4[] invBindMatrices = new Matrix4x4[joints.Count]; for (int i = 0; i < invBindMatrices.Length; ++i) { // Generates inverseWorldMatrix in right-handed coordinate system // Manually converts world translation and rotation from left to right handed coordinates systems Vector3 pos = joints[i].position; Quaternion rot = joints[i].rotation; convertQuatLeftToRightHandedness(ref rot); convertVector3LeftToRightHandedness(ref pos); invBindMatrices[i] = Matrix4x4.TRS(pos, rot, joints[i].lossyScale).inverse *sceneRootMatrix.inverse; } invBindMatricesAccessor.Populate(invBindMatrices, m); invBindMatricesAccessorIndex = invBindAccessorIndex; }
public void Populate(Transform m, ref GlTF_Accessor invBindMatricesAccessor, int invBindAccessorIndex) { SkinnedMeshRenderer skinMesh = m.GetComponent <SkinnedMeshRenderer>(); if (!skinMesh) { return; } // Populate bind poses. From https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html: // The bind pose is bone's inverse transformation matrix // In this case we also make this matrix relative to the root // So that we can move the root game object around freely Mesh mesh = skinMesh.sharedMesh; Matrix4x4[] invBindMatrices = new Matrix4x4[skinMesh.sharedMesh.bindposes.Length]; for (int i = 0; i < invBindMatrices.Length; ++i) { // Generates inverseWorldMatrix in right-handed coordinate system // Manually converts world translation and rotation from left to right handed coordinates systems Vector3 pos = skinMesh.bones[i].position; Quaternion rot = skinMesh.bones[i].rotation; convertQuatLeftToRightHandedness(ref rot); convertVector3LeftToRightHandedness(ref pos); invBindMatrices[i] = Matrix4x4.TRS(pos, rot, skinMesh.bones[i].lossyScale).inverse *sceneRootMatrix.inverse; } invBindMatricesAccessor.Populate(invBindMatrices, m); invBindMatricesAccessorIndex = invBindAccessorIndex; // Fill jointNames jointNames = new string[skinMesh.bones.Length]; for (int i = 0; i < skinMesh.bones.Length; ++i) { jointNames[i] = GlTF_Node.GetNameFromObject(skinMesh.bones[i]); } }
public void Populate(SkinnedMeshRenderer smr, List <Transform> skeletons) { name = GetNameFromObject(smr.transform); if (smr.rootBone != null) { boneNames = new List <string>(); List <Matrix4x4> boneMats = new List <Matrix4x4>(); var parent = smr.rootBone.parent; if (parent != null) { var mat = Matrix4x4.TRS(parent.localPosition, parent.localRotation, parent.localScale); bindShape = new GlTF_Matrix(mat); } else { bindShape = new GlTF_Matrix(Matrix4x4.identity); } bindShape.name = "bindShapeMatrix"; skeletons.Add(smr.rootBone); for (var i = 0; i < smr.bones.Length; ++i) { var found = IsBoneInHierarchy(smr.rootBone, smr.bones[i]); if (!found) { // set as its own skeleton to prevent error if it doesn't get included in rootBone hierarchy skeletons.Add(smr.bones[i]); } boneNames.Add(GlTF_Node.GetNameFromObject(smr.bones[i])); boneMats.Add(smr.sharedMesh.bindposes[i]); } ibmAccessor = new GlTF_Accessor("accessor_ibm_" + name, GlTF_Accessor.Type.MAT4, GlTF_Accessor.ComponentType.FLOAT); ibmAccessor.bufferView = GlTF_Writer.mat4BufferView; ibmAccessor.Populate(boneMats.ToArray()); GlTF_Writer.accessors.Add(ibmAccessor); } }
public void Populate(AnimationClipCurveData curveData) { string propName = curveData.propertyName; if (times == null) // allocate one array of times, assumes all channels have same number of keys { timeAccessor = new GlTF_Accessor(name + "TimeAccessor", GlTF_Accessor.Type.SCALAR, GlTF_Accessor.ComponentType.FLOAT); timeAccessor.bufferView = GlTF_Writer.floatBufferView; GlTF_Writer.accessors.Add(timeAccessor); times = new float[curveData.curve.keys.Length]; for (int i = 0; i < curveData.curve.keys.Length; i++) { times[i] = curveData.curve.keys[i].time; } timeAccessor.Populate(times); } if (propName.Contains("m_LocalPosition")) { if (positions == null) { translationAccessor = new GlTF_Accessor(name + "TranslationAccessor", GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); translationAccessor.bufferView = GlTF_Writer.vec3BufferView; GlTF_Writer.accessors.Add(translationAccessor); positions = new Vector3[curveData.curve.keys.Length]; } if (propName.Contains(".x")) { px = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { positions[i].x = curveData.curve.keys[i].value; } } else if (propName.Contains(".y")) { py = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { positions[i].y = curveData.curve.keys[i].value; } } else if (propName.Contains(".z")) { pz = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { positions[i].z = curveData.curve.keys[i].value; } } if (px && py && pz) { translationAccessor.Populate(positions); } } if (propName.Contains("m_LocalScale")) { if (scales == null) { scaleAccessor = new GlTF_Accessor(name + "ScaleAccessor", GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); scaleAccessor.bufferView = GlTF_Writer.vec3BufferView; GlTF_Writer.accessors.Add(scaleAccessor); scales = new Vector3[curveData.curve.keys.Length]; } if (propName.Contains(".x")) { sx = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { scales[i].x = curveData.curve.keys[i].value; } } else if (propName.Contains(".y")) { sy = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { scales[i].y = curveData.curve.keys[i].value; } } else if (propName.Contains(".z")) { sz = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { scales[i].z = curveData.curve.keys[i].value; } } if (sx && sy && sz) { scaleAccessor.Populate(scales); } } if (propName.Contains("m_LocalRotation")) { if (rotations == null) { rotationAccessor = new GlTF_Accessor(name + "RotationAccessor", GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.FLOAT); rotationAccessor.bufferView = GlTF_Writer.vec4BufferView; GlTF_Writer.accessors.Add(rotationAccessor); rotations = new Vector4[curveData.curve.keys.Length]; } if (propName.Contains(".x")) { rx = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { rotations[i].x = curveData.curve.keys[i].value; } } else if (propName.Contains(".y")) { ry = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { rotations[i].y = curveData.curve.keys[i].value; } } else if (propName.Contains(".z")) { rz = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { rotations[i].z = curveData.curve.keys[i].value; } } else if (propName.Contains(".w")) { rw = true; for (int i = 0; i < curveData.curve.keys.Length; i++) { rotations[i].w = curveData.curve.keys[i].value; } } if (rx && ry && rz && rw) { rotationAccessor.Populate(scales); } } }
public void Populate(AnimationClip clip, Transform tr, bool bake = true) { // 1. browse clip, collect all curves and create a TargetCurveSet for each target Dictionary <string, TargetCurveSet> targetCurvesBinding = new Dictionary <string, TargetCurveSet>(); collectClipCurves(clip, ref targetCurvesBinding); // Baking needs all properties, fill missing curves with transform data in 2 keyframes (start, endTime) // where endTime is clip duration generateMissingCurves(clip.length, ref tr, ref targetCurvesBinding); if (bake) { // Bake animation for all animated nodes foreach (string target in targetCurvesBinding.Keys) { Transform targetTr = target.Length > 0 ? tr.Find(target) : tr; if (targetTr == null) { continue; } Transform targetObject = targetTr; string targetId = GlTF_Node.GetNameFromObject(targetObject); // Initialize accessors for current animation GlTF_Accessor timeAccessor = new GlTF_Accessor(targetId + "_TimeAccessor_" + clip.name, GlTF_Accessor.Type.SCALAR, GlTF_Accessor.ComponentType.FLOAT); timeAccessor.bufferView = GlTF_Writer.floatBufferView; int timeAccessorIndex = GlTF_Writer.accessors.Count; GlTF_Writer.accessors.Add(timeAccessor); // Translation GlTF_Channel chTranslation = new GlTF_Channel("translation", animSamplers.Count); GlTF_Target targetTranslation = new GlTF_Target(); targetTranslation.id = targetId; targetTranslation.path = "translation"; chTranslation.target = targetTranslation; channels.Add(chTranslation); GlTF_AnimSampler sTranslation = new GlTF_AnimSampler(timeAccessorIndex, GlTF_Writer.accessors.Count); GlTF_Accessor translationAccessor = new GlTF_Accessor(targetId + "_TranslationAccessor_" + clip.name, GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); translationAccessor.bufferView = GlTF_Writer.vec3BufferView; GlTF_Writer.accessors.Add(translationAccessor); animSamplers.Add(sTranslation); // Rotation GlTF_Channel chRotation = new GlTF_Channel("rotation", animSamplers.Count); GlTF_Target targetRotation = new GlTF_Target(); targetRotation.id = GlTF_Node.GetNameFromObject(targetObject); targetRotation.path = "rotation"; chRotation.target = targetRotation; channels.Add(chRotation); GlTF_AnimSampler sRotation = new GlTF_AnimSampler(timeAccessorIndex, GlTF_Writer.accessors.Count); GlTF_Accessor rotationAccessor = new GlTF_Accessor(targetId + "_RotationAccessor_" + clip.name, GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.FLOAT); rotationAccessor.bufferView = GlTF_Writer.vec4BufferView; GlTF_Writer.accessors.Add(rotationAccessor); animSamplers.Add(sRotation); // Scale GlTF_Channel chScale = new GlTF_Channel("scale", animSamplers.Count); GlTF_Target targetScale = new GlTF_Target(); targetScale.id = GlTF_Node.GetNameFromObject(targetObject); targetScale.path = "scale"; chScale.target = targetScale; channels.Add(chScale); GlTF_AnimSampler sScale = new GlTF_AnimSampler(timeAccessorIndex, GlTF_Writer.accessors.Count); GlTF_Accessor scaleAccessor = new GlTF_Accessor(targetId + "_ScaleAccessor_" + clip.name, GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); scaleAccessor.bufferView = GlTF_Writer.vec3BufferView; GlTF_Writer.accessors.Add(scaleAccessor); animSamplers.Add(sScale); // Bake and populate animation data float[] times = null; Vector3[] positions = null; Vector3[] scales = null; Vector4[] rotations = null; bakeCurveSet(targetCurvesBinding[target], clip.length, bakingFramerate, ref times, ref positions, ref rotations, ref scales); // Populate accessors timeAccessor.Populate(times); translationAccessor.Populate(positions); rotationAccessor.Populate(rotations, false); scaleAccessor.Populate(scales, true); } } else { Debug.LogError("Only baked animation is supported for now. Skipping animation"); } }
public void Populate(Mesh m) { // attributes.Populate (m); indices.Populate(m.GetTriangles(index), true); }
private void PopulateUv( int channel, TiltBrush.GeometryPool pool, GlTF_Accessor accessor, Semantic semantic) { bool packVertId = m_layout.PackVertexIdIntoTexcoord1W && channel == 1; if (packVertId) { // Guaranteed by GlTF_VertexLayout Debug.Assert(m_layout.m_tbLayout.GetTexcoordInfo(channel).size == 3); Debug.Assert(m_layout.GetTexcoordSize(channel) == 4); } if (accessor == null) { return; } if (channel < 0 || channel > 3) { throw new ArgumentException("Invalid channel"); } TiltBrush.GeometryPool.TexcoordData texcoordData = pool.GetTexcoordData(channel); if (semantic == Semantic.XyIsUvZIsDistance && accessor.type != GlTF_Accessor.Type.VEC3) { throw new ArgumentException("XyIsUvZIsDistance semantic can only be applied to VEC3"); } bool flipY; if (semantic == Semantic.Unspecified && channel == 0 && accessor.type == GlTF_Accessor.Type.VEC2) { Debug.LogWarning("Assuming Semantic.XyIsUv"); semantic = Semantic.XyIsUv; } switch (semantic) { case Semantic.Position: case Semantic.Vector: case Semantic.Timestamp: flipY = false; break; case Semantic.XyIsUvZIsDistance: case Semantic.XyIsUv: flipY = true; break; default: throw new ArgumentException("semantic"); } switch (accessor.type) { case GlTF_Accessor.Type.SCALAR: throw new NotImplementedException(); case GlTF_Accessor.Type.VEC2: accessor.Populate(texcoordData.v2, flipY: flipY, calculateMinMax: false); break; case GlTF_Accessor.Type.VEC3: accessor.Populate(texcoordData.v3, flipY: flipY, calculateMinMax: false); break; case GlTF_Accessor.Type.VEC4: if (packVertId) { // In the vertexId case, we actually have a vec3, which needs to be augmented to a vec4. // TODO: this should happen at some higher level. int i = 0; var v4 = texcoordData.v3.ConvertAll <Vector4>((v => new Vector4(v.x, v.y, v.z, i++))); accessor.Populate(v4, flipY: flipY, calculateMinMax: false); } else { accessor.Populate(texcoordData.v4, flipY: flipY, calculateMinMax: false); } break; default: throw new ArgumentException("Unexpected accessor.type"); } }
public void PopulateAccessor(AnimationClipCurveData cd, Keyframe[] refKeyFrames) { string propName = cd.propertyName; if (type == Type.Translation || type == Type.Scale) { if (v3 == null) { v3 = new Vector3[refKeyFrames.Length]; } if (propName.Contains(".x")) { wx = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v3[i].x = cd.curve.Evaluate(refKeyFrames[i].time); } } else if (propName.Contains(".y")) { wy = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v3[i].y = cd.curve.Evaluate(refKeyFrames[i].time); } } else if (propName.Contains(".z")) { wz = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v3[i].z = cd.curve.Evaluate(refKeyFrames[i].time); } } if (wx && wy && wz) { accessor.Populate(v3); } } else { if (v4 == null) { v4 = new Vector4[refKeyFrames.Length]; } if (propName.Contains(".x")) { wx = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v4[i].x = cd.curve.Evaluate(refKeyFrames[i].time); } } else if (propName.Contains(".y")) { wy = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v4[i].y = cd.curve.Evaluate(refKeyFrames[i].time); } } else if (propName.Contains(".z")) { wz = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v4[i].z = cd.curve.Evaluate(refKeyFrames[i].time); } } else if (propName.Contains(".w")) { ww = true; for (var i = 0; i < refKeyFrames.Length; ++i) { v4[i].w = cd.curve.Evaluate(refKeyFrames[i].time); } } if (wx && wy && wz && ww) { accessor.Populate(v4); } } }
public void Populate (AnimationClipCurveData curveData) { string propName = curveData.propertyName; if (times == null) // allocate one array of times, assumes all channels have same number of keys { timeAccessor = new GlTF_Accessor(name+"TimeAccessor", "SCALAR", "FLOAT"); timeAccessor.bufferView = GlTF_Writer.floatBufferView; GlTF_Writer.accessors.Add (timeAccessor); times = new float[curveData.curve.keys.Length]; for (int i = 0; i < curveData.curve.keys.Length; i++) times[i] = curveData.curve.keys[i].time; timeAccessor.Populate (times); } if (propName.Contains("m_LocalPosition")) { if (positions == null) { translationAccessor = new GlTF_Accessor(name+"TranslationAccessor", "VEC3", "FLOAT"); translationAccessor.bufferView = GlTF_Writer.vec3BufferView; GlTF_Writer.accessors.Add (translationAccessor); positions = new Vector3[curveData.curve.keys.Length]; } if (propName.Contains (".x")) { px = true; for (int i = 0; i < curveData.curve.keys.Length; i++) positions[i].x = curveData.curve.keys[i].value; } else if (propName.Contains (".y")) { py = true; for (int i = 0; i < curveData.curve.keys.Length; i++) positions[i].y = curveData.curve.keys[i].value; } else if (propName.Contains (".z")) { pz = true; for (int i = 0; i < curveData.curve.keys.Length; i++) positions[i].z = curveData.curve.keys[i].value; } if (px && py && pz) translationAccessor.Populate (positions); } if (propName.Contains("m_LocalScale")) { if (scales == null) { scaleAccessor = new GlTF_Accessor(name+"ScaleAccessor", "VEC3", "FLOAT"); scaleAccessor.bufferView = GlTF_Writer.vec3BufferView; GlTF_Writer.accessors.Add (scaleAccessor); scales = new Vector3[curveData.curve.keys.Length]; } if (propName.Contains (".x")) { sx = true; for (int i = 0; i < curveData.curve.keys.Length; i++) scales[i].x = curveData.curve.keys[i].value; } else if (propName.Contains (".y")) { sy = true; for (int i = 0; i < curveData.curve.keys.Length; i++) scales[i].y = curveData.curve.keys[i].value; } else if (propName.Contains (".z")) { sz = true; for (int i = 0; i < curveData.curve.keys.Length; i++) scales[i].z = curveData.curve.keys[i].value; } if (sx && sy && sz) scaleAccessor.Populate (scales); } if (propName.Contains("m_LocalRotation")) { if (rotations == null) { rotationAccessor = new GlTF_Accessor(name+"RotationAccessor", "VEC4", "FLOAT"); rotationAccessor.bufferView = GlTF_Writer.vec4BufferView; GlTF_Writer.accessors.Add (rotationAccessor); rotations = new Vector4[curveData.curve.keys.Length]; } if (propName.Contains (".x")) { rx = true; for (int i = 0; i < curveData.curve.keys.Length; i++) rotations[i].x = curveData.curve.keys[i].value; } else if (propName.Contains (".y")) { ry = true; for (int i = 0; i < curveData.curve.keys.Length; i++) rotations[i].y = curveData.curve.keys[i].value; } else if (propName.Contains (".z")) { rz = true; for (int i = 0; i < curveData.curve.keys.Length; i++) rotations[i].z = curveData.curve.keys[i].value; } else if (propName.Contains (".w")) { rw = true; for (int i = 0; i < curveData.curve.keys.Length; i++) rotations[i].w = curveData.curve.keys[i].value; } if (rx && ry && rz && rw) rotationAccessor.Populate (scales); } }