716 lines
27 KiB
C#
716 lines
27 KiB
C#
/*
|
|
* Copyright (c) 2006-2016, openmetaverse.co
|
|
* Copyright (c) 2021-2024, Sjofn LLC.
|
|
* All rights reserved.
|
|
*
|
|
* - Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* - Neither the name of the openmetaverse.co nor the names
|
|
* of its contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text.RegularExpressions;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using System.Linq;
|
|
using System.Xml.Serialization;
|
|
using CoreJ2K;
|
|
using OpenMetaverse.ImportExport.Collada14;
|
|
using OpenMetaverse.Rendering;
|
|
using SkiaSharp;
|
|
|
|
namespace OpenMetaverse.ImportExport
|
|
{
|
|
/// <summary>
|
|
/// Parsing Collada model files into data structures
|
|
/// </summary>
|
|
public class ColladaLoader
|
|
{
|
|
COLLADA Model;
|
|
static XmlSerializer Serializer = null;
|
|
List<Node> Nodes;
|
|
List<ModelMaterial> Materials;
|
|
Dictionary<string, string> MatSymTarget;
|
|
string FileName;
|
|
|
|
class Node
|
|
{
|
|
public Matrix4 Transform = Matrix4.Identity;
|
|
public string Name;
|
|
public string ID;
|
|
public string MeshID;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses Collada document
|
|
/// </summary>
|
|
/// <param name="filename">Load .dae model from this file</param>
|
|
/// <param name="loadImages">Load and decode images for uploading with model</param>
|
|
/// <returns>A list of mesh prims that were parsed from the collada file</returns>
|
|
public List<ModelPrim> Load(string filename, bool loadImages)
|
|
{
|
|
try
|
|
{
|
|
// Create an instance of the XmlSerializer specifying type and namespace.
|
|
if (Serializer == null)
|
|
{
|
|
Serializer = new XmlSerializer(typeof(COLLADA));
|
|
}
|
|
|
|
this.FileName = filename;
|
|
|
|
// A FileStream is needed to read the XML document.
|
|
FileStream fs = new FileStream(filename, FileMode.Open);
|
|
XmlReader reader = XmlReader.Create(fs);
|
|
Model = (COLLADA)Serializer.Deserialize(reader);
|
|
fs.Close();
|
|
var prims = Parse();
|
|
if (loadImages)
|
|
{
|
|
LoadImages(prims);
|
|
}
|
|
return prims;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Failed parsing collada file: " + ex.Message, Helpers.LogLevel.Error, ex);
|
|
return new List<ModelPrim>();
|
|
}
|
|
}
|
|
|
|
void LoadImages(IEnumerable<ModelPrim> prims)
|
|
{
|
|
foreach (var prim in prims)
|
|
{
|
|
foreach (var face in prim.Faces)
|
|
{
|
|
if (!string.IsNullOrEmpty(face.Material.Texture))
|
|
{
|
|
LoadImage(face.Material);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LoadImage(ModelMaterial material)
|
|
{
|
|
var fname = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(FileName), material.Texture);
|
|
|
|
try
|
|
{
|
|
string ext = System.IO.Path.GetExtension(material.Texture).ToLower();
|
|
|
|
SKBitmap bitmap;
|
|
|
|
switch (ext)
|
|
{
|
|
case ".jp2":
|
|
case ".j2c":
|
|
material.TextureData = File.ReadAllBytes(fname);
|
|
return;
|
|
case ".tga":
|
|
case ".targa":
|
|
bitmap = Imaging.Targa.Decode(fname);
|
|
break;
|
|
default:
|
|
var img = SKImage.FromEncodedData(fname);
|
|
bitmap = SKBitmap.FromImage(img);
|
|
break;
|
|
}
|
|
|
|
int width = bitmap.Width;
|
|
int height = bitmap.Height;
|
|
|
|
// Handle resizing to prevent excessively large images and irregular dimensions
|
|
if (!IsPowerOfTwo((uint)width) || !IsPowerOfTwo((uint)height) || width > 1024 || height > 1024)
|
|
{
|
|
var origWidth = width;
|
|
var origHieght = height;
|
|
|
|
width = ClosestPowerOwTwo(width);
|
|
height = ClosestPowerOwTwo(height);
|
|
|
|
width = width > 1024 ? 1024 : width;
|
|
height = height > 1024 ? 1024 : height;
|
|
|
|
Logger.Log($"Image has irregular dimensions {origWidth}x{origHieght}. Resizing to {width}x{height}",
|
|
Helpers.LogLevel.Info);
|
|
|
|
var info = new SKImageInfo(width, height);
|
|
var scaledImage = new SKBitmap(info);
|
|
bitmap.ScalePixels(scaledImage.PeekPixels(), new SKSamplingOptions(SKFilterMode.Linear));
|
|
bitmap.Dispose();
|
|
bitmap = scaledImage;
|
|
}
|
|
|
|
material.TextureData = Imaging.J2K.ToBytes(bitmap);
|
|
|
|
Logger.Log($"Successfully encoded {fname}", Helpers.LogLevel.Info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log($"Failed loading {fname}: {ex.Message}", Helpers.LogLevel.Warning);
|
|
}
|
|
|
|
}
|
|
|
|
bool IsPowerOfTwo(uint n)
|
|
{
|
|
return (n & (n - 1)) == 0 && n != 0;
|
|
}
|
|
|
|
int ClosestPowerOwTwo(int n)
|
|
{
|
|
int res = 1;
|
|
|
|
while (res < n)
|
|
{
|
|
res <<= 1;
|
|
}
|
|
|
|
return res > 1 ? res / 2 : 1;
|
|
}
|
|
|
|
ModelMaterial ExtractMaterial(object diffuse)
|
|
{
|
|
ModelMaterial ret = new ModelMaterial();
|
|
if (diffuse is common_color_or_texture_typeColor color)
|
|
{
|
|
ret.DiffuseColor = new Color4((float)color.Values[0], (float)color.Values[1], (float)color.Values[2], (float)color.Values[3]);
|
|
}
|
|
else if (diffuse is common_color_or_texture_typeTexture tex)
|
|
{
|
|
ret.Texture = tex.texcoord;
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
|
|
void ParseMaterials()
|
|
{
|
|
|
|
if (Model == null) return;
|
|
|
|
Materials = new List<ModelMaterial>();
|
|
|
|
// Material -> effect mapping
|
|
var matEffect = new Dictionary<string, string>();
|
|
var tmpEffects = new List<ModelMaterial>();
|
|
|
|
// Image ID -> filename mapping
|
|
Dictionary<string, string> imgMap = new Dictionary<string, string>();
|
|
|
|
foreach (var item in Model.Items)
|
|
{
|
|
if (item is library_images images)
|
|
{
|
|
if (images.image != null)
|
|
{
|
|
foreach (var image in images.image)
|
|
{
|
|
var img = (image)image;
|
|
string ID = img.id;
|
|
if (img.Item is string imgItem)
|
|
{
|
|
imgMap[ID] = imgItem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var item in Model.Items)
|
|
{
|
|
if (item is library_materials materials)
|
|
{
|
|
if (materials.material != null)
|
|
{
|
|
foreach (var material in materials.material)
|
|
{
|
|
var ID = material.id;
|
|
if (!string.IsNullOrEmpty(material.instance_effect?.url))
|
|
{
|
|
matEffect[material.instance_effect.url.Substring(1)] = ID;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var item in Model.Items)
|
|
{
|
|
if (item is library_effects effects)
|
|
{
|
|
if (effects.effect != null)
|
|
{
|
|
foreach (var effect in effects.effect)
|
|
{
|
|
string ID = effect.id;
|
|
foreach (var effItem in effect.Items)
|
|
{
|
|
if (effItem is effectFx_profile_abstractProfile_COMMON common)
|
|
{
|
|
var teq = common.technique;
|
|
if (teq != null)
|
|
{
|
|
if (teq.Item is effectFx_profile_abstractProfile_COMMONTechniquePhong phong)
|
|
{
|
|
if (phong.diffuse != null)
|
|
{
|
|
var material = ExtractMaterial(phong.diffuse.Item);
|
|
material.ID = ID;
|
|
tmpEffects.Add(material);
|
|
}
|
|
}
|
|
else if (teq.Item is effectFx_profile_abstractProfile_COMMONTechniqueLambert shader)
|
|
{
|
|
if (shader.diffuse != null)
|
|
{
|
|
var material = ExtractMaterial(shader.diffuse.Item);
|
|
material.ID = ID;
|
|
tmpEffects.Add(material);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var effect in tmpEffects)
|
|
{
|
|
if (matEffect.TryGetValue(effect.ID, out var effectId))
|
|
{
|
|
effect.ID = effectId;
|
|
if (!string.IsNullOrEmpty(effect.Texture))
|
|
{
|
|
if (imgMap.TryGetValue(effect.Texture, out var effectTexture))
|
|
{
|
|
effect.Texture = effectTexture;
|
|
}
|
|
}
|
|
Materials.Add(effect);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessNode(node node)
|
|
{
|
|
Node n = new Node {Name = node.name, ID = node.id};
|
|
|
|
if (node.Items != null)
|
|
{
|
|
// Try finding matrix
|
|
foreach (var i in node.Items)
|
|
{
|
|
if (i is matrix mtx)
|
|
{
|
|
for (int a = 0; a < 4; a++)
|
|
{
|
|
for (int b = 0; b < 4; b++)
|
|
{
|
|
n.Transform[b, a] = (float)mtx.Values[a * 4 + b];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find geometry and material
|
|
if (node.instance_geometry != null && node.instance_geometry.Length > 0)
|
|
{
|
|
var instGeom = node.instance_geometry[0];
|
|
if (!string.IsNullOrEmpty(instGeom.url))
|
|
{
|
|
n.MeshID = instGeom.url.Substring(1);
|
|
}
|
|
if (instGeom.bind_material?.technique_common != null)
|
|
{
|
|
foreach (var teq in instGeom.bind_material.technique_common)
|
|
{
|
|
var target = teq.target;
|
|
if (string.IsNullOrEmpty(target)) continue;
|
|
|
|
target = target.Substring(1);
|
|
MatSymTarget[teq.symbol] = target;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node.Items != null && node.instance_geometry != null && node.instance_geometry.Length > 0)
|
|
Nodes.Add(n);
|
|
|
|
// Recurse if the scene is hierarchical
|
|
if (node.node1 != null)
|
|
{
|
|
foreach (node nd in node.node1)
|
|
ProcessNode(nd);
|
|
}
|
|
}
|
|
|
|
void ParseVisualScene()
|
|
{
|
|
Nodes = new List<Node>();
|
|
if (Model == null) return;
|
|
|
|
MatSymTarget = new Dictionary<string, string>();
|
|
|
|
foreach (var item in Model.Items)
|
|
{
|
|
if (item is library_visual_scenes scenes)
|
|
{
|
|
var scene = scenes.visual_scene[0];
|
|
foreach (var node in scene.node)
|
|
{
|
|
ProcessNode(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
List<ModelPrim> Parse()
|
|
{
|
|
var Prims = new List<ModelPrim>();
|
|
|
|
float DEG_TO_RAD = 0.017453292519943295769236907684886f;
|
|
|
|
if (Model == null) return Prims;
|
|
|
|
Matrix4 transform = Matrix4.Identity;
|
|
|
|
UpAxisType upAxis = UpAxisType.Y_UP;
|
|
|
|
var asset = Model.asset;
|
|
if (asset != null)
|
|
{
|
|
upAxis = asset.up_axis;
|
|
if (asset.unit != null)
|
|
{
|
|
float meter = (float)asset.unit.meter;
|
|
transform[0, 0] = meter;
|
|
transform[1, 1] = meter;
|
|
transform[2, 2] = meter;
|
|
}
|
|
}
|
|
|
|
Matrix4 rotation = Matrix4.Identity;
|
|
|
|
if (upAxis == UpAxisType.X_UP)
|
|
{
|
|
rotation = Matrix4.CreateFromEulers(0.0f, 90.0f * DEG_TO_RAD, 0.0f);
|
|
}
|
|
else if (upAxis == UpAxisType.Y_UP)
|
|
{
|
|
rotation = Matrix4.CreateFromEulers(90.0f * DEG_TO_RAD, 0.0f, 0.0f);
|
|
}
|
|
|
|
rotation = rotation * transform;
|
|
transform = rotation;
|
|
|
|
ParseVisualScene();
|
|
ParseMaterials();
|
|
|
|
foreach (var item in Model.Items) {
|
|
if (item is library_geometries geometries) {
|
|
foreach (var geo in geometries.geometry) {
|
|
var mesh = geo.Item as mesh;
|
|
if (mesh == null)
|
|
continue;
|
|
|
|
var nodes = Nodes.FindAll(n => n.MeshID == geo.id); // Find all instances of this geometry
|
|
if (nodes != null) {
|
|
ModelPrim firstPrim = null; // The first prim is actually calculated, the others are just copied from it.
|
|
|
|
Vector3 asset_scale = new Vector3(1,1,1);
|
|
Vector3 asset_offset = new Vector3(0, 0, 0); // Scale and offset between Collada and OS asset (Which is always in a unit cube)
|
|
|
|
foreach (var node in nodes) {
|
|
var prim = new ModelPrim
|
|
{
|
|
ID = node.ID
|
|
};
|
|
Prims.Add(prim);
|
|
|
|
// First node is used to create the asset. This is as the code to crate the byte array is somewhat
|
|
// erroneously placed in the ModelPrim class.
|
|
if (firstPrim == null) {
|
|
firstPrim = prim;
|
|
AddPositions(out asset_scale, out asset_offset, mesh, prim, transform); // transform is used only for inch -> meter and up axis transform.
|
|
|
|
foreach (var mitem in mesh.Items) {
|
|
if (mitem is triangles triangles)
|
|
AddFacesFromPolyList(Triangles2Polylist(triangles), mesh, prim, transform); // Transform is used to turn normals according to up axis
|
|
if (mitem is polylist polylist)
|
|
AddFacesFromPolyList(polylist, mesh, prim, transform);
|
|
}
|
|
|
|
prim.CreateAsset(UUID.Zero);
|
|
}
|
|
else {
|
|
// Copy the values set by Addpositions and AddFacesFromPolyList as these are the same as long as the mesh is the same
|
|
prim.Asset = firstPrim.Asset;
|
|
prim.BoundMin = firstPrim.BoundMin;
|
|
prim.BoundMax = firstPrim.BoundMax;
|
|
prim.Positions = firstPrim.Positions;
|
|
prim.Faces = firstPrim.Faces;
|
|
}
|
|
|
|
// Note: This ignores any shear or similar non-linear effects. This can cause some problems but it
|
|
// is unlikely that authoring software can generate such matrices.
|
|
node.Transform.Decompose(out prim.Scale, out prim.Rotation, out prim.Position);
|
|
float roll, pitch, yaw;
|
|
node.Transform.GetEulerAngles(out roll, out pitch, out yaw);
|
|
|
|
// The offset created when normalizing the mesh vertices into the OS unit cube must be rotated
|
|
// before being added to the position part of the Collada transform.
|
|
Matrix4 rot = Matrix4.CreateFromQuaternion(prim.Rotation); // Convert rotation to matrix for for Transform
|
|
Vector3 offset = Vector3.Transform(asset_offset * prim.Scale, rot); // The offset must be rotated and mutiplied by the Collada file's scale as the offset is added during rendering with the unit cube mesh already multiplied by the compound scale.
|
|
prim.Position += offset;
|
|
prim.Scale *= asset_scale; // Modify scale from Collada instance by the rescaling done in AddPositions()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Prims;
|
|
}
|
|
|
|
source FindSource(IEnumerable<source> sources, string id)
|
|
{
|
|
id = id.Substring(1);
|
|
|
|
return sources.FirstOrDefault(src => src.id == id);
|
|
}
|
|
|
|
void AddPositions(out Vector3 scale, out Vector3 offset, mesh mesh, ModelPrim prim, Matrix4 transform)
|
|
{
|
|
prim.Positions = new List<Vector3>();
|
|
source posSrc = FindSource(mesh.source, mesh.vertices.input[0].source);
|
|
double[] posVals = ((float_array)posSrc.Item).Values;
|
|
|
|
for (int i = 0; i < posVals.Length / 3; i++)
|
|
{
|
|
Vector3 pos = new Vector3((float)posVals[i * 3], (float)posVals[i * 3 + 1], (float)posVals[i * 3 + 2]);
|
|
pos = Vector3.Transform(pos, transform);
|
|
prim.Positions.Add(pos);
|
|
}
|
|
|
|
prim.BoundMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
|
prim.BoundMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
|
|
|
foreach (var pos in prim.Positions)
|
|
{
|
|
if (pos.X > prim.BoundMax.X) prim.BoundMax.X = pos.X;
|
|
if (pos.Y > prim.BoundMax.Y) prim.BoundMax.Y = pos.Y;
|
|
if (pos.Z > prim.BoundMax.Z) prim.BoundMax.Z = pos.Z;
|
|
|
|
if (pos.X < prim.BoundMin.X) prim.BoundMin.X = pos.X;
|
|
if (pos.Y < prim.BoundMin.Y) prim.BoundMin.Y = pos.Y;
|
|
if (pos.Z < prim.BoundMin.Z) prim.BoundMin.Z = pos.Z;
|
|
}
|
|
|
|
scale = prim.BoundMax - prim.BoundMin;
|
|
offset = prim.BoundMin + (scale / 2);
|
|
|
|
// Fit vertex positions into identity cube -0.5 .. 0.5
|
|
for (int i = 0; i < prim.Positions.Count; i++)
|
|
{
|
|
Vector3 pos = prim.Positions[i];
|
|
pos = new Vector3(
|
|
scale.X == 0 ? 0 : ((pos.X - prim.BoundMin.X) / scale.X) - 0.5f,
|
|
scale.Y == 0 ? 0 : ((pos.Y - prim.BoundMin.Y) / scale.Y) - 0.5f,
|
|
scale.Z == 0 ? 0 : ((pos.Z - prim.BoundMin.Z) / scale.Z) - 0.5f
|
|
);
|
|
prim.Positions[i] = pos;
|
|
}
|
|
}
|
|
|
|
int[] StrToArray(string s)
|
|
{
|
|
string[] vals = Regex.Split(s.Trim(), @"\s+");
|
|
int[] ret = new int[vals.Length];
|
|
|
|
for (int i = 0; i < ret.Length; i++)
|
|
{
|
|
int.TryParse(vals[i], out ret[i]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void AddFacesFromPolyList(polylist list, mesh mesh, ModelPrim prim, Matrix4 transform)
|
|
{
|
|
string material = list.material;
|
|
source posSrc = null;
|
|
source normalSrc = null;
|
|
source uvSrc = null;
|
|
|
|
ulong stride = 0;
|
|
int posOffset = -1;
|
|
int norOffset = -1;
|
|
int uvOffset = -1;
|
|
|
|
foreach (var inp in list.input)
|
|
{
|
|
stride = Math.Max(stride, inp.offset);
|
|
|
|
switch (inp.semantic)
|
|
{
|
|
case "VERTEX":
|
|
posSrc = FindSource(mesh.source, mesh.vertices.input[0].source);
|
|
posOffset = (int)inp.offset;
|
|
break;
|
|
case "NORMAL":
|
|
normalSrc = FindSource(mesh.source, inp.source);
|
|
norOffset = (int)inp.offset;
|
|
break;
|
|
case "TEXCOORD":
|
|
uvSrc = FindSource(mesh.source, inp.source);
|
|
uvOffset = (int)inp.offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
stride += 1;
|
|
|
|
if (posSrc == null) return;
|
|
|
|
var vcount = StrToArray(list.vcount);
|
|
var idx = StrToArray(list.p);
|
|
|
|
Vector3[] normals = null;
|
|
if (normalSrc != null)
|
|
{
|
|
var norVal = ((float_array)normalSrc.Item).Values;
|
|
normals = new Vector3[norVal.Length / 3];
|
|
|
|
for (int i = 0; i < normals.Length; i++)
|
|
{
|
|
normals[i] = new Vector3((float)norVal[i * 3 + 0], (float)norVal[i * 3 + 1], (float)norVal[i * 3 + 2]);
|
|
normals[i] = Vector3.TransformNormal(normals[i], transform);
|
|
normals[i].Normalize();
|
|
}
|
|
}
|
|
|
|
Vector2[] uvs = null;
|
|
if (uvSrc != null)
|
|
{
|
|
var uvVal = ((float_array)uvSrc.Item).Values;
|
|
uvs = new Vector2[uvVal.Length / 2];
|
|
|
|
for (int i = 0; i < uvs.Length; i++)
|
|
{
|
|
uvs[i] = new Vector2((float)uvVal[i * 2 + 0], (float)uvVal[i * 2 + 1]);
|
|
}
|
|
|
|
}
|
|
|
|
ModelFace face = new ModelFace {MaterialID = list.material};
|
|
if (face.MaterialID != null)
|
|
{
|
|
if (MatSymTarget.TryGetValue(list.material, out var value))
|
|
{
|
|
ModelMaterial mat = Materials.Find(m => m.ID == value);
|
|
if (mat != null)
|
|
{
|
|
face.Material = mat;
|
|
}
|
|
}
|
|
}
|
|
|
|
int curIdx = 0;
|
|
|
|
foreach (var nvert in vcount)
|
|
{
|
|
if (nvert < 3 || nvert > 4)
|
|
{
|
|
throw new InvalidDataException("Only triangles and quads supported");
|
|
}
|
|
|
|
Vertex[] verts = new Vertex[nvert];
|
|
for (int j = 0; j < nvert; j++)
|
|
{
|
|
verts[j].Position = prim.Positions[idx[curIdx + posOffset + (int)stride * j]];
|
|
|
|
if (normals != null)
|
|
{
|
|
verts[j].Normal = normals[idx[curIdx + norOffset + (int)stride * j]];
|
|
}
|
|
|
|
if (uvs != null)
|
|
{
|
|
verts[j].TexCoord = uvs[idx[curIdx + uvOffset + (int)stride * j]];
|
|
}
|
|
}
|
|
|
|
|
|
switch (nvert)
|
|
{
|
|
case 3:
|
|
face.AddVertex(verts[0]);
|
|
face.AddVertex(verts[1]);
|
|
face.AddVertex(verts[2]);
|
|
break;
|
|
case 4:
|
|
face.AddVertex(verts[0]);
|
|
face.AddVertex(verts[1]);
|
|
face.AddVertex(verts[2]);
|
|
|
|
face.AddVertex(verts[0]);
|
|
face.AddVertex(verts[2]);
|
|
face.AddVertex(verts[3]);
|
|
break;
|
|
}
|
|
|
|
curIdx += (int)stride * nvert;
|
|
}
|
|
|
|
prim.Faces.Add(face);
|
|
|
|
|
|
}
|
|
|
|
polylist Triangles2Polylist(triangles triangles)
|
|
{
|
|
polylist poly = new polylist
|
|
{
|
|
count = triangles.count,
|
|
input = triangles.input,
|
|
material = triangles.material,
|
|
name = triangles.name,
|
|
p = triangles.p
|
|
};
|
|
|
|
const string str = "3 ";
|
|
System.Text.StringBuilder builder = new System.Text.StringBuilder(str.Length * (int)poly.count);
|
|
for (int i = 0; i < (int)poly.count; i++) builder.Append(str);
|
|
poly.vcount = builder.ToString();
|
|
|
|
return poly;
|
|
}
|
|
|
|
}
|
|
}
|