public GlobalPhotonMap() { bounds = new BoundingBox(); hasRadiance = false; maxPower = 0; maxRadius = 0; }
public void build(PrimitiveList primitives) { this.primitives = primitives; int n = primitives.getNumPrimitives(); UI.printDetailed(UI.Module.ACCEL, "Getting bounding box ..."); bounds = primitives.getWorldBounds(null); objects = new int[n]; for (int i = 0; i < n; i++) objects[i] = i; UI.printDetailed(UI.Module.ACCEL, "Creating tree ..."); int initialSize = 3 * (2 * 6 * n + 1); List<int> tempTree = new List<int>((initialSize + 3) / 4); BuildStats stats = new BuildStats(); Timer t = new Timer(); t.start(); buildHierarchy(tempTree, objects, stats); t.end(); UI.printDetailed(UI.Module.ACCEL, "Trimming tree ..."); tree = tempTree.ToArray(); // display stats stats.printStats(); UI.printDetailed(UI.Module.ACCEL, " * Creation time: %s", t); UI.printDetailed(UI.Module.ACCEL, " * Usage of init: %3d%%", 100 * tree.Length / initialSize); UI.printDetailed(UI.Module.ACCEL, " * Tree memory: %s", Memory.SizeOf(tree)); UI.printDetailed(UI.Module.ACCEL, " * Indices memory: %s", Memory.SizeOf(objects)); }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(1.5f); if (o2w != null) bounds = o2w.transform(bounds); return bounds; }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(); foreach (Instance i in instances) bounds.include(i.getBounds()); return bounds; }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(); if (o2w == null) { for (int i = 0; i < patches.Length; i++) { float[] patch = patches[i]; for (int j = 0; j < patch.Length; j += 3) bounds.include(patch[j], patch[j + 1], patch[j + 2]); } } else { // transform vertices first for (int i = 0; i < patches.Length; i++) { float[] patch = patches[i]; for (int j = 0; j < patch.Length; j += 3) { float x = patch[j]; float y = patch[j + 1]; float z = patch[j + 2]; float wx = o2w.transformPX(x, y, z); float wy = o2w.transformPY(x, y, z); float wz = o2w.transformPZ(x, y, z); bounds.include(wx, wy, wz); } } } return bounds; }
public UniformGrid() { nx = ny = nz = 0; bounds = null; cells = null; voxelwx = voxelwy = voxelwz = 0; invVoxelwx = invVoxelwy = invVoxelwz = 0; }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(minX, minY, minZ); bounds.include(maxX, maxY, maxZ); if (o2w == null) return bounds; return o2w.transform(bounds); }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(-ro - ri, -ro - ri, -ri); bounds.include(ro + ri, ro + ri, ri); if (o2w != null) bounds = o2w.transform(bounds); return bounds; }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(); for (int i = 0, i3 = 0; i < n; i++, i3 += 3) bounds.include(particles[i3], particles[i3 + 1], particles[i3 + 2]); bounds.include(bounds.getMinimum().x - r, bounds.getMinimum().y - r, bounds.getMinimum().z - r); bounds.include(bounds.getMaximum().x + r, bounds.getMaximum().y + r, bounds.getMaximum().z + r); return o2w == null ? bounds : o2w.transform(bounds); }
public Instance() { o2w = new MovingMatrix4(null); w2o = new MovingMatrix4(null); bounds = null; geometry = null; shaders = null; modifiers = null; }
public CausticPhotonMap(Options options) { _numEmit = options.getInt("caustics.emit", 10000); gatherNum = options.getInt("caustics.gather", 50); gatherRadius = options.getFloat("caustics.radius", 0.5f); filterValue = options.getFloat("caustics.filter", 1.1f); bounds = new BoundingBox(); maxPower = 0; maxRadius = 0; }
public GlobalPhotonMap(int numEmit, int numGather, float gatherRadius) { this._numEmit = numEmit; this.numGather = numGather; this.gatherRadius = gatherRadius; bounds = new BoundingBox(); hasRadiance = false; maxPower = 0; maxRadius = 0; }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(); if (o2w == null) { for (int i = 0; i < points.Length; i += 3) bounds.include(points[i], points[i + 1], points[i + 2]); } else { // transform vertices first for (int i = 0; i < points.Length; i += 3) { float x = points[i]; float y = points[i + 1]; float z = points[i + 2]; float wx = o2w.transformPX(x, y, z); float wy = o2w.transformPY(x, y, z); float wz = o2w.transformPZ(x, y, z); bounds.include(wx, wy, wz); } } return bounds; }
public void prepare(Options options, BoundingBox sceneBounds) { // get settings _numEmit = options.getInt("gi.irr-cache.gmap.emit", 100000); numGather = options.getInt("gi.irr-cache.gmap.gather", 50); gatherRadius = options.getFloat("gi.irr-cache.gmap.radius", 0.5f); // init bounds = new BoundingBox(sceneBounds); bounds.enlargeUlps(); Vector3 w = bounds.getExtents(); nx = (int)Math.Max(((w.x / gatherRadius) + 0.5f), 1); ny = (int)Math.Max(((w.y / gatherRadius) + 0.5f), 1); nz = (int)Math.Max(((w.z / gatherRadius) + 0.5f), 1); int numCells = nx * ny * nz; UI.printInfo(UI.Module.LIGHT, "Initializing grid photon map:"); UI.printInfo(UI.Module.LIGHT, " * Resolution: {0}x{1}x{2}", nx, ny, nz); UI.printInfo(UI.Module.LIGHT, " * Total cells: {0}", numCells); for (hashPrime = 0; hashPrime < PRIMES.Length; hashPrime++) if (PRIMES[hashPrime] > (numCells / 5)) break; cellHash = new PhotonGroup[PRIMES[hashPrime]]; UI.printInfo(UI.Module.LIGHT, " * Initial hash size: {0}", cellHash.Length); }
/** * Returns <code>true</code> if the specified bounding box intersects this * one. The boxes are treated as volumes, so a box inside another will * return true. Returns <code>false</code> if the parameter is * <code>null</code>. * * @param b box to be tested for intersection * @return <code>true</code> if the boxes overlap, <code>false</code> * otherwise */ public bool intersects(BoundingBox b) { return((b != null) && (minimum.x <= b.maximum.x) && (maximum.x >= b.minimum.x) && (minimum.y <= b.maximum.y) && (maximum.y >= b.minimum.y) && (minimum.z <= b.maximum.z) && (maximum.z >= b.minimum.z)); }
/** * Recompute world space bounding box of this instance. */ public void updateBounds() { bounds = geometry.getWorldBounds(o2w.getData(0)); for (int i = 1; i < o2w.numSegments(); i++) bounds.include(geometry.getWorldBounds(o2w.getData(i))); }
/** * Creates a copy of the given box. * * @param b bounding box to copy */ public BoundingBox(BoundingBox b) { minimum = new Point3(b.minimum); maximum = new Point3(b.maximum); }
public void prepare(BoundingBox sceneBounds) { bounds = new BoundingBox(sceneBounds); bounds.enlargeUlps(); Vector3 w = bounds.getExtents(); nx = (int)Math.Max(((w.x / gatherRadius) + 0.5f), 1); ny = (int)Math.Max(((w.y / gatherRadius) + 0.5f), 1); nz = (int)Math.Max(((w.z / gatherRadius) + 0.5f), 1); int numCells = nx * ny * nz; UI.printInfo(UI.Module.LIGHT, "Initializing grid photon map:"); UI.printInfo(UI.Module.LIGHT, " * Resolution: %dx%dx%d", nx, ny, nz); UI.printInfo(UI.Module.LIGHT, " * Total cells: %d", numCells); for (hashPrime = 0; hashPrime < PRIMES.Length; hashPrime++) if (PRIMES[hashPrime] > (numCells / 5)) break; cellHash = new PhotonGroup[PRIMES[hashPrime]]; UI.printInfo(UI.Module.LIGHT, " * Initial hash size: %d", cellHash.Length); }
public void prepare(BoundingBox sceneBounds) { photonList = new List<Photon>(); photonList.Add(null); photons = null; storedPhotons = halfStoredPhotons = 0; }
public bool update(ParameterList pl, SunflowAPI api) { ParameterList.FloatParameter pts = pl.getPointArray("points"); if (pts != null) { BoundingBox bounds = new BoundingBox(); for (int i = 0; i < pts.data.Length; i += 3) bounds.include(pts.data[i], pts.data[i + 1], pts.data[i + 2]); // cube extents minX = bounds.getMinimum().x; minY = bounds.getMinimum().y; minZ = bounds.getMinimum().z; maxX = bounds.getMaximum().x; maxY = bounds.getMaximum().y; maxZ = bounds.getMaximum().z; } return true; }
private int dumpObj(int offset, int vertOffset, int maxN, BoundingBox bounds, StreamWriter file, StreamWriter mtlFile) { if (offset == 0) file.WriteLine(string.Format("mtllib {0}.mtl", dumpPrefix)); int nextOffset = tree[offset]; if ((nextOffset & (3 << 30)) == (3 << 30)) { // leaf int n = tree[offset + 1]; if (n > 0) { // output the current voxel to the file Point3 min = bounds.getMinimum(); Point3 max = bounds.getMaximum(); file.WriteLine(string.Format("o node{0}", offset)); file.WriteLine(string.Format("v {0} {1} {2}", max.x, max.y, min.z)); file.WriteLine(string.Format("v {0} {1} {2}", max.x, min.y, min.z)); file.WriteLine(string.Format("v {0} {1} {2}", min.x, min.y, min.z)); file.WriteLine(string.Format("v {0} {1} {2}", min.x, max.y, min.z)); file.WriteLine(string.Format("v {0} {1} {2}", max.x, max.y, max.z)); file.WriteLine(string.Format("v {0} {1} {2}", max.x, min.y, max.z)); file.WriteLine(string.Format("v {0} {1} {2}", min.x, min.y, max.z)); file.WriteLine(string.Format("v {0} {1} {2}", min.x, max.y, max.z)); int v0 = vertOffset; file.WriteLine(string.Format("usemtl mtl{0}", n)); file.WriteLine("s off"); file.WriteLine(string.Format("f {0} {1} {2} {3}", v0 + 1, v0 + 2, v0 + 3, v0 + 4)); file.WriteLine(string.Format("f {0} {1} {2} {3}", v0 + 5, v0 + 8, v0 + 7, v0 + 6)); file.WriteLine(string.Format("f {0} {1} {2} {3}", v0 + 1, v0 + 5, v0 + 6, v0 + 2)); file.WriteLine(string.Format("f {0} {1} {2} {3}", v0 + 2, v0 + 6, v0 + 7, v0 + 3)); file.WriteLine(string.Format("f {0} {1} {2} {3}", v0 + 3, v0 + 7, v0 + 8, v0 + 4)); file.WriteLine(string.Format("f {0} {1} {2} {3}", v0 + 5, v0 + 1, v0 + 4, v0 + 8)); vertOffset += 8; } return vertOffset; } else { // node, recurse int axis = nextOffset & (3 << 30), v0; float split = ByteUtil.intBitsToFloat(tree[offset + 1]), min, max; nextOffset &= ~(3 << 30); switch (axis) { case 0: max = bounds.getMaximum().x; bounds.getMaximum().x = split; v0 = dumpObj(nextOffset, vertOffset, maxN, bounds, file, mtlFile); // restore and go to other side bounds.getMaximum().x = max; min = bounds.getMinimum().x; bounds.getMinimum().x = split; v0 = dumpObj(nextOffset + 2, v0, maxN, bounds, file, mtlFile); bounds.getMinimum().x = min; break; case 1 << 30: max = bounds.getMaximum().y; bounds.getMaximum().y = split; v0 = dumpObj(nextOffset, vertOffset, maxN, bounds, file, mtlFile); // restore and go to other side bounds.getMaximum().y = max; min = bounds.getMinimum().y; bounds.getMinimum().y = split; v0 = dumpObj(nextOffset + 2, v0, maxN, bounds, file, mtlFile); bounds.getMinimum().y = min; break; case 2 << 30: max = bounds.getMaximum().z; bounds.getMaximum().z = split; v0 = dumpObj(nextOffset, vertOffset, maxN, bounds, file, mtlFile); // restore and go to other side bounds.getMaximum().z = max; min = bounds.getMinimum().z; bounds.getMinimum().z = split; v0 = dumpObj(nextOffset + 2, v0, maxN, bounds, file, mtlFile); // restore and go to other side bounds.getMinimum().z = min; break; default: v0 = vertOffset; break; } return v0; } }
public void build(PrimitiveList primitives) { UI.printDetailed(UI.Module.ACCEL, "KDTree settings"); UI.printDetailed(UI.Module.ACCEL, " * Max Leaf Size: {0}", maxPrims); UI.printDetailed(UI.Module.ACCEL, " * Max Depth: {0}", MAX_DEPTH); UI.printDetailed(UI.Module.ACCEL, " * Traversal cost: {0}", TRAVERSAL_COST); UI.printDetailed(UI.Module.ACCEL, " * Intersect cost: {0}", INTERSECT_COST); UI.printDetailed(UI.Module.ACCEL, " * Empty bonus: {0}", EMPTY_BONUS); UI.printDetailed(UI.Module.ACCEL, " * Dump leaves: {0}", dump ? "enabled" : "disabled"); Timer total = new Timer(); total.start(); primitiveList = primitives; // get the object space bounds bounds = primitives.getWorldBounds(null); int nPrim = primitiveList.getNumPrimitives(), nSplits = 0; BuildTask task = new BuildTask(nPrim); Timer prepare = new Timer(); prepare.start(); for (int i = 0; i < nPrim; i++) { for (int axis = 0; axis < 3; axis++) { float ls = primitiveList.getPrimitiveBound(i, 2 * axis + 0); float rs = primitiveList.getPrimitiveBound(i, 2 * axis + 1); if (ls == rs) { // flat in this dimension task.splits[nSplits] = pack(ls, PLANAR, axis, i); nSplits++; } else { task.splits[nSplits + 0] = pack(ls, OPENED, axis, i); task.splits[nSplits + 1] = pack(rs, CLOSED, axis, i); nSplits += 2; } } } task.n = nSplits; prepare.end(); Timer t = new Timer(); List<int> tempTree = new List<int>(); List<int> tempList = new List<int>(); tempTree.Add(0); tempTree.Add(1); t.start(); // sort it Timer sorting = new Timer(); sorting.start(); radix12(task.splits, task.n); sorting.end(); // build the actual tree BuildStats stats = new BuildStats(); buildTree(bounds.getMinimum().x, bounds.getMaximum().x, bounds.getMinimum().y, bounds.getMaximum().y, bounds.getMinimum().z, bounds.getMaximum().z, task, 1, tempTree, 0, tempList, stats); t.end(); // write out arrays // free some memory task = null; tree = tempTree.ToArray(); tempTree = null; this.primitives = tempList.ToArray(); tempList = null; total.end(); // display some extra info stats.printStats(); UI.printDetailed(UI.Module.ACCEL, " * Node memory: {0}", Memory.SizeOf(tree)); UI.printDetailed(UI.Module.ACCEL, " * Object memory: {0}", Memory.SizeOf(this.primitives)); UI.printDetailed(UI.Module.ACCEL, " * Prepare time: {0}", prepare); UI.printDetailed(UI.Module.ACCEL, " * Sorting time: {0}", sorting); UI.printDetailed(UI.Module.ACCEL, " * Tree creation: {0}", t); UI.printDetailed(UI.Module.ACCEL, " * Build time: {0}", total); if (dump) { try { UI.printInfo(UI.Module.ACCEL, "Dumping mtls to {0}.mtl ...", dumpPrefix); StreamWriter mtlFile = new StreamWriter(dumpPrefix + ".mtl"); int maxN = stats.maxObjects; for (int n = 0; n <= maxN; n++) { float blend = (float)n / (float)maxN; Color nc; if (blend < 0.25) nc = Color.blend(Color.BLUE, Color.GREEN, blend / 0.25f); else if (blend < 0.5) nc = Color.blend(Color.GREEN, Color.YELLOW, (blend - 0.25f) / 0.25f); else if (blend < 0.75) nc = Color.blend(Color.YELLOW, Color.RED, (blend - 0.50f) / 0.25f); else nc = Color.MAGENTA; mtlFile.WriteLine(string.Format("newmtl mtl{0}", n)); float[] rgb = nc.getRGB(); mtlFile.WriteLine("Ka 0.1 0.1 0.1"); mtlFile.WriteLine(string.Format("Kd {0}g {1}g {2}g", rgb[0], rgb[1], rgb[2])); mtlFile.WriteLine("illum 1\n"); } StreamWriter objFile = new StreamWriter(dumpPrefix + ".obj"); UI.printInfo(UI.Module.ACCEL, "Dumping tree to {0}.obj ...", dumpPrefix); dumpObj(0, 0, maxN, new BoundingBox(bounds), objFile, mtlFile); objFile.Close(); mtlFile.Close(); } catch (Exception e) { Console.WriteLine(e); } } }
/** * Returns <code>true</code> if the specified bounding box intersects this * one. The boxes are treated as volumes, so a box inside another will * return true. Returns <code>false</code> if the parameter is * <code>null</code>. * * @param b box to be tested for intersection * @return <code>true</code> if the boxes overlap, <code>false</code> * otherwise */ public bool intersects(BoundingBox b) { return ((b != null) && (minimum.x <= b.maximum.x) && (maximum.x >= b.minimum.x) && (minimum.y <= b.maximum.y) && (maximum.y >= b.minimum.y) && (minimum.z <= b.maximum.z) && (maximum.z >= b.minimum.z)); }
private void updateGeometry(Point3 c0, Point3 c1) { // figure out cube extents lightBounds = new BoundingBox(c0); lightBounds.include(c1); // cube extents minX = lightBounds.getMinimum().x; minY = lightBounds.getMinimum().y; minZ = lightBounds.getMinimum().z; maxX = lightBounds.getMaximum().x; maxY = lightBounds.getMaximum().y; maxZ = lightBounds.getMaximum().z; // work around epsilon problems for light test lightBounds.enlargeUlps(); // light source geometry lxmin = maxX / 3 + 2 * minX / 3; lxmax = minX / 3 + 2 * maxX / 3; lymin = maxY / 3 + 2 * minY / 3; lymax = minY / 3 + 2 * maxY / 3; area = (lxmax - lxmin) * (lymax - lymin); }
public bool intersects(BoundingBox box) { // this could be optimized BoundingBox b = new BoundingBox(); b.include(new Point3(minX, minY, minZ)); b.include(new Point3(maxX, maxY, maxZ)); if (b.intersects(box)) { // the box is overlapping or enclosed if (!b.contains(new Point3(box.getMinimum().x, box.getMinimum().y, box.getMinimum().z))) return true; if (!b.contains(new Point3(box.getMinimum().x, box.getMinimum().y, box.getMaximum().z))) return true; if (!b.contains(new Point3(box.getMinimum().x, box.getMaximum().y, box.getMinimum().z))) return true; if (!b.contains(new Point3(box.getMinimum().x, box.getMaximum().y, box.getMaximum().z))) return true; if (!b.contains(new Point3(box.getMaximum().x, box.getMinimum().y, box.getMinimum().z))) return true; if (!b.contains(new Point3(box.getMaximum().x, box.getMinimum().y, box.getMaximum().z))) return true; if (!b.contains(new Point3(box.getMaximum().x, box.getMaximum().y, box.getMinimum().z))) return true; if (!b.contains(new Point3(box.getMaximum().x, box.getMaximum().y, box.getMaximum().z))) return true; // all vertices of the box are inside - the surface of the box is // not intersected } return false; }
public CubeGrid() { nx = ny = nz = 1; bounds = new BoundingBox(1); }
/** * Changes the extents of the box as needed to include the given box into * this box. Does nothing if the parameter is <code>null</code>. * * @param b box to be included */ public void include(BoundingBox b) { if (b != null) { if (b.minimum.x < minimum.x) minimum.x = b.minimum.x; if (b.maximum.x > maximum.x) maximum.x = b.maximum.x; if (b.minimum.y < minimum.y) minimum.y = b.minimum.y; if (b.maximum.y > maximum.y) maximum.y = b.maximum.y; if (b.minimum.z < minimum.z) minimum.z = b.minimum.z; if (b.maximum.z > maximum.z) maximum.z = b.maximum.z; } }
public void build(PrimitiveList primitives) { Timer t = new Timer(); t.start(); this.primitives = primitives; int n = primitives.getNumPrimitives(); // compute bounds bounds = primitives.getWorldBounds(null); // create grid from number of objects bounds.enlargeUlps(); Vector3 w = bounds.getExtents(); double s = Math.Pow((w.x * w.y * w.z) / n, 1 / 3.0); nx = MathUtils.clamp((int)((w.x / s) + 0.5), 1, 128); ny = MathUtils.clamp((int)((w.y / s) + 0.5), 1, 128); nz = MathUtils.clamp((int)((w.z / s) + 0.5), 1, 128); voxelwx = w.x / nx; voxelwy = w.y / ny; voxelwz = w.z / nz; invVoxelwx = 1 / voxelwx; invVoxelwy = 1 / voxelwy; invVoxelwz = 1 / voxelwz; UI.printDetailed(UI.Module.ACCEL, "Creating grid: %dx%dx%d ...", nx, ny, nz); List<int>[] buildCells = new List<int>[nx * ny * nz]; // add all objects into the grid cells they overlap int[] imin = new int[3]; int[] imax = new int[3]; int numCellsPerObject = 0; for (int i = 0; i < n; i++) { getGridIndex(primitives.getPrimitiveBound(i, 0), primitives.getPrimitiveBound(i, 2), primitives.getPrimitiveBound(i, 4), imin); getGridIndex(primitives.getPrimitiveBound(i, 1), primitives.getPrimitiveBound(i, 3), primitives.getPrimitiveBound(i, 5), imax); for (int ix = imin[0]; ix <= imax[0]; ix++) { for (int iy = imin[1]; iy <= imax[1]; iy++) { for (int iz = imin[2]; iz <= imax[2]; iz++) { int idx = ix + (nx * iy) + (nx * ny * iz); if (buildCells[idx] == null) buildCells[idx] = new List<int>(); buildCells[idx].Add(i); numCellsPerObject++; } } } } UI.printDetailed(UI.Module.ACCEL, "Building cells ..."); int numEmpty = 0; int numInFull = 0; cells = new int[nx * ny * nz][]; //int i = 0; //foreach (List<int> cell in buildCells) for (int i = 0; i < buildCells.Length; i++) { if (buildCells[i] != null) { if (buildCells[i].Count == 0) { numEmpty++; buildCells[i] = null; } else { cells[i] = buildCells[i].ToArray(); numInFull += buildCells[i].Count; } } else numEmpty++; //i++; } t.end(); UI.printDetailed(UI.Module.ACCEL, "Uniform grid statistics:"); UI.printDetailed(UI.Module.ACCEL, " * Grid cells: {0}", cells.Length); UI.printDetailed(UI.Module.ACCEL, " * Used cells: {0}", cells.Length - numEmpty); UI.printDetailed(UI.Module.ACCEL, " * Empty cells: {0}", numEmpty); UI.printDetailed(UI.Module.ACCEL, " * Occupancy: {0}", 100.0 * (cells.Length - numEmpty) / cells.Length); UI.printDetailed(UI.Module.ACCEL, " * Objects/Cell: {0}", (double)numInFull / (double)cells.Length); UI.printDetailed(UI.Module.ACCEL, " * Objects/Used Cell: {0}", (double)numInFull / (double)(cells.Length - numEmpty)); UI.printDetailed(UI.Module.ACCEL, " * Cells/Object: {0}", (double)numCellsPerObject / (double)n); UI.printDetailed(UI.Module.ACCEL, " * Build time: {0}", t.ToString()); }
public void prepare(BoundingBox sceneBounds) { }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(getPrimitiveBound(0, 1)); if (o2w != null) bounds = o2w.transform(bounds); return bounds; }
public BoundingBox getWorldBounds(Matrix4 o2w) { BoundingBox bounds = new BoundingBox(); if (o2w == null) { for (int i = 0; i < triangleMesh.uvs.data.Length; i += 2) bounds.include(triangleMesh.uvs.data[i], triangleMesh.uvs.data[i + 1], 0); } else { // transform vertices first for (int i = 0; i < triangleMesh.uvs.data.Length; i += 2) { float x = triangleMesh.uvs.data[i]; float y = triangleMesh.uvs.data[i + 1]; float wx = o2w.transformPX(x, y, 0); float wy = o2w.transformPY(x, y, 0); float wz = o2w.transformPZ(x, y, 0); bounds.include(wx, wy, wz); } } return bounds; }