- Home /
array out of range while updating mesh variables
Original Question :
I have a string that is converted to a custom text mesh.
Whenever the size of the Built-In arrays are changed, for the first frame/loop/cycle I get out of range errors :
Mesh.vertices is too small. The supplied vertex array has less vertices than are referenced by the triangles array. UnityEngine.Mesh:set_vertices(Vector3[]) SText:UpdateTextChar() (at Assets/_Custom/Sprites/SText.js:145) SText:UpdateText(String) (at Assets/_Custom/Sprites/SText.js:172) UI_Manager:UpdateStats() (at Assets/_Scripts/UI_Manager.js:202) UI_Manager:Update() (at Assets/_Scripts/UI_Manager.js:102)
Mesh.uv is out of bounds. The supplied array needs to be the same size as the Mesh.vertices array. UnityEngine.Mesh:set_uv(Vector2[]) SText:UpdateTextChar() (at Assets/_Custom/Sprites/SText.js:146) SText:UpdateText(String) (at Assets/_Custom/Sprites/SText.js:172) UI_Manager:UpdateStats() (at Assets/_Scripts/UI_Manager.js:202) UI_Manager:Update() (at Assets/_Scripts/UI_Manager.js:102)
Mesh.normals is out of bounds. The supplied array needs to be the same size as the Mesh.vertices array. UnityEngine.Mesh:set_normals(Vector3[]) SText:UpdateTextChar() (at Assets/_Custom/Sprites/SText.js:148) SText:UpdateText(String) (at Assets/_Custom/Sprites/SText.js:172) UI_Manager:UpdateStats() (at Assets/_Scripts/UI_Manager.js:202) UI_Manager:Update() (at Assets/_Scripts/UI_Manager.js:102)
I cannot understand it, as this worked without error when running Unity 3.5.1, but now running 3.5.6 I have this error across all projects where I have used my sprite and text classes.
The arrays are set by the length of the string before the mesh is generated in UpdateTextMesh(). I have tried merging this function with UpdateTextChar() and using helloStringLength instead of .length in every subsequent for-loop, but it makes no diference. I cannot see how the arrays are being assigned the wrong length for the first cycle when helloStringLength changes size.
Here is my SText Script :
#pragma strict
@script RequireComponent(MeshFilter, MeshRenderer)
#if UNITY_EDITOR
@ContextMenu ("Construct sText")
function ConstructText()
{
Debug.Log("Constructing sText from ContextMenu");
ConstructOnCreation();
}
function ConstructOnCreation()
{
if(!myTextMesh)
{
GetComponent(MeshFilter).mesh = myTextMesh = new Mesh();
myTextMesh.name = "sTextMesh";
}
UpdateText( helloString );
}
#endif
public class SText extends MonoBehaviour
{
public var textSheetInfo : TextAsset;
public var textureWidth : int;
public var textureHeight : int;
public var helloString : String = "Hello";
public var charSize : float = 1.0;
public var _char_id : int[];
public var _char_x : int[];
public var _char_y : int[];
public var _char_width : int[];
public var _char_height : int[];
public var _char_xoffset : int[];
public var _char_yoffset : int[];
public var _char_xadvance : int[];
private var myTextMesh : Mesh;
private var uv : Vector2[];
private var verts : Vector3[];
private var tris : int[];
private var normals : Vector3[];
private var size : Vector2 = Vector2.one;
private var offset : Vector2 = Vector2(0.0, 0.0);
private var uvSize : Vector2 = Vector2(1.0, 1.0);
private var uvOffset : Vector2 = Vector2(0.0, 0.0);
private var letter : String;
private var textAlignX : float = 0.0;
private var textAlignY : float = 0.0;
public var textPaddingX : float = 0.0;
function Awake()
{
if(!myTextMesh)
{
GetComponent(MeshFilter).mesh = myTextMesh = new Mesh();
myTextMesh.name = "sTextMesh";
}
UpdateText( helloString );
}
function Start()
{
yield WaitForSeconds( 2.0 );
UpdateText( "Droogie" );
yield WaitForSeconds( 2.0 );
UpdateText( "Droogie !..!, ('.') ,!..!" );
}
function Update() {
}
// CONSTRUCTING Text
function UpdateTextMesh()
{
var helloStringLength : int = helloString.Length;
verts = new Vector3[helloStringLength * 4];
uv = new Vector2[helloStringLength * 4];
tris = new int[helloStringLength * 6];
normals = new Vector3[helloStringLength * 4];
myTextMesh = this.transform.GetComponent(MeshFilter).mesh as Mesh;
var readString : int = 0;
textAlignX = 0;
textAlignY = 0;
for (readString = 0; readString < helloStringLength; readString ++)
{
letter = helloString.Substring( readString, 1 );
var uniString : int = (letter[0]);
uvSize = Vector2( parseFloat(_char_width[uniString]) / parseFloat(textureWidth),
parseFloat(_char_height[uniString]) / parseFloat(textureHeight) );
uvOffset = Vector2( parseFloat(_char_x[uniString]) / parseFloat(textureWidth),
parseFloat(_char_y[uniString]) / parseFloat(textureHeight) );
size = Vector2( parseFloat(_char_width[uniString]), parseFloat(_char_height[uniString]) );
offset = Vector2( parseFloat(_char_xoffset[uniString]), parseFloat(_char_yoffset[uniString]) );
textAlignX += _char_xoffset[uniString];
textAlignY = 0 - ( _char_yoffset[uniString] * 0.5 );
// BuildTextChar
verts[(readString * 4) + 0] = new Vector3( ( textAlignX + offset.x ) * (charSize / 64), ( 0 - offset.y ) * (charSize / 64), 0);
verts[(readString * 4) + 1] = new Vector3( ( textAlignX + offset.x + size.x ) * (charSize / 64), ( 0 - offset.y ) * (charSize / 64), 0);
verts[(readString * 4) + 2] = new Vector3( ( textAlignX + offset.x ) * (charSize / 64), ( 0 - offset.y - size.y ) * (charSize / 64), 0);
verts[(readString * 4) + 3] = new Vector3( ( textAlignX + offset.x + size.x ) * (charSize / 64), ( 0 - offset.y - size.y ) * (charSize / 64), 0);
uv[(readString * 4) + 2] = new Vector2(uvOffset.x, 1.0 - uvOffset.y - uvSize.y);
uv[(readString * 4) + 3] = new Vector2(uvOffset.x + uvSize.x, 1.0 - uvOffset.y - uvSize.y);
uv[(readString * 4) + 0] = new Vector2(uvOffset.x, 1.0 - uvOffset.y);
uv[(readString * 4) + 1] = new Vector2(uvOffset.x + uvSize.x, 1.0 - uvOffset.y);
tris[(readString * 6) + 0] = (readString * 4) + 0;
tris[(readString * 6) + 1] = (readString * 4) + 1;
tris[(readString * 6) + 2] = (readString * 4) + 2;
tris[(readString * 6) + 3] = (readString * 4) + 2;
tris[(readString * 6) + 4] = (readString * 4) + 1;
tris[(readString * 6) + 5] = (readString * 4) + 3;
textAlignX += textPaddingX + _char_xadvance[uniString];
}
myTextMesh.vertices = verts;
myTextMesh.uv = uv;
myTextMesh.triangles = tris;
myTextMesh.normals = normals;
myTextMesh.RecalculateBounds();
myTextMesh.RecalculateNormals();
// Calculate Tangents
TangentSolver(myTextMesh);
}
// UPDATING Text
public function UpdateText( textString : String ) // TEXT
{
helloString = textString;
//transform.position = Vector3(posX, posY, layer); // transform.position.z
//textPaddingX = padding; // 0.0;
UpdateTextMesh();
}
}
Here is the script I am using to update the speed :
public var speedText : SText;
function Update()
{
speedText.UpdateText( parseInt( currentSpeed ).ToString() );
}
EDIT :
From the advice I have received from Kryptos and phodges, the title of the question should now be :
How do I correctly create and optimize my meshes, and correctly modify the vertex information per change in vertices alone, and/or triangles & uvs length or values ?
Since I learned mesh generation, my procedure was as follows :
declare builtin arrays to store and calculate the vertex information
declare the size of these arrays
calculate verts / uvs / tris / normals
get the mesh filter / mesh
assign the mesh vertex arrays the same length as the builtin arrays' lengths
assign all the values from the builtin arrays to the mesh vertices etc
RecalculateBounds and RecalculateNormals
when I am animating vertices per update :
use the builtin arrays and make calculations
simply assign mesh.vertices = builtin array verts;
this can be seen in the scripts of all my progressive mesh related questions :
http://answers.unity3d.com/questions/277222/simply-assign-tangents-to-single-quad.html
http://answers.unity3d.com/questions/305515/irregularities-with-procedurally-generated-sphere.html
http://answers.unity3d.com/questions/314373/most-efficient-way-to-declare-and-assign-variables.html
So am now wondering : is the correct methd of creating and optimizing mesh generation and manipulation is to :
find the mesh filter mesh
directly assign the lengths to the different vertex arrays
directly assign values to the different vertex arrays
RecalculateBounds and RecalculateNormals
when I am animating vertices per update :
find the mesh filter mesh
clear the mesh
directly assign the lengths to the different vertex arrays
directly assign values to the different vertex arrays
RecalculateBounds and RecalculateNormals
If I am doing something wrong please let me know, I am self taught, only used Unity this year, coming from a very limited ability using basic and actionscript2 (mostly just logic-gate calcs and printout stuff) and admit I need to know more about many things (I don't have a clue about shaders, stack and flow, many other optimization or efficiency techniques, or just the right way to use Unity). If you can guide me I am very grateful.
Not related to your issue but these lines are useless:
myText$$anonymous$$esh.vertices = new Vector3[verts.length];
myText$$anonymous$$esh.uv = new Vector2[uv.length];
myText$$anonymous$$esh.triangles = new int[tris.length];
myText$$anonymous$$esh.normals = new Vector3[normals.length];
Since you reaffect arrays in the following lines.
Thanks for that information. It was an attempt to again assign the length of the arrays correctly to clear the error of the incorrect vert to tri lengths. I have edited my question asking for more information.
Answer by Kryptos · Oct 19, 2012 at 03:19 PM
Don't reuse a mesh, always create a new one (it is safer):
// don't do
myTextMesh = this.transform.GetComponent<MeshFilter>().mesh;
// do that instead
myTextMesh = new Mesh();
// and after creation
this.GetComponent<MeshFilter>().mesh = myTextMesh;
Or if you don't like recreating then don't forget to clear the mesh (especially when the number of triangles changes):
myTextMesh.Clear();
edit: Sorry I mixed up C# and US. Now all my code example are in C#.
This is what I tried :
//GetComponent($$anonymous$$eshFilter).mesh = myText$$anonymous$$esh = new $$anonymous$$esh();
myText$$anonymous$$esh = new $$anonymous$$esh();
this.GetComponent().mesh = myText$$anonymous$$esh;
myText$$anonymous$$esh.name = "sText$$anonymous$$esh";
and these are my errors :
Assets/_Custom/Sprites/SText.js(16,39): BCE0043: Unexpected token: ).
Assets/_Custom/Sprites/SText.js(16,40): BCE0044: expecting ), found '.'.
Assets/_Custom/Sprites/SText.js(16,41): UCE0001: ';' expected. Insert a semicolon at the end.
these are all on the line
this.GetComponent<$$anonymous$$eshFilter>().mesh = myText$$anonymous$$esh;
do I have the format of this correct for uJS ?
GetComponent<$$anonymous$$eshFilter>()
Thanks.
Thanks, I should have realized this is just an expanded variation on what I already use on creation :
//GetComponent($$anonymous$$eshFilter).mesh = myText$$anonymous$$esh = new $$anonymous$$esh();
myText$$anonymous$$esh = new $$anonymous$$esh();
this.GetComponent($$anonymous$$eshFilter).mesh = myText$$anonymous$$esh;
Answer by andisopany · Oct 19, 2012 at 02:47 AM
Are you taking the default mesh and adding values to it before setting its size?
I didn't look too close but it looks like you are getting your mesh from the gameobject and then stuffing values into it. How do you know its the right size?
hmm, it says you are supposed to call Clear() on the mesh before assigning the vertices.
I am building my own mesh, and the arrays are populated by another script (a parser that reads the bmfont text file and stuffs the information in). The text displays and changes to the text all work as expected, even with the error.
now, imagine helloString is "10", when the length of helloString changes eg "100" so length has gone from 2 to 3. then on the first execution of the function UpdateText() I get the errors, but the next frame/loop/cycle the mesh updates, it populates the arrays with no error, it displays and works fine.
The arrays are set at the start of the function, before any vertices are calculated. And I cannot see a way that helloSting.length is changing between assigning the length of the arrays and the assigning the values in those arrays.
Again, this only happens once when the length of helloString changes.
Thanks for your comments though =]
As this isn't a solution, could you please convert this to comment? Thanks =]
Answer by phodges · Oct 19, 2012 at 05:44 AM
In your code, you are setting the arrays twice; once to some unused arrays of the same size as your data and then directly afterwards. That seems a little redundant so I would get rid of the first stage and also do as anisopany suggests and call clear on the mesh before the assignment.
With every script that I create a mesh (be it a quad, icosahedron, sphere, 'sock', etc), I do all my calculations and store them in local cache'd builtin arrays, then when the calculations are complete I assign the information in these arrays to the variables of the mesh. I have examples of procedurally generated meshes that have the vertices animated per frame all without ever using Clear(). This is mostly due to the fact that most of my meshes don't change in length. As this one does, I shall indeed try Clear(); but to reiterate the problem, the errors only occur once when the length of helloString changes. The script works fine in every other aspect. Thanks.
The more I look at it, the less I like the redundant assignments (and array allocation). Do try removing those lines.
I agree with @phodges. There are useless and even expensive because the internal $$anonymous$$esh is recreated twice.
I admit I need to learn and understand more. Am rewriting the SText class now considering this (phodges) and $$anonymous$$ryptos answer.
Answer by phodges · Oct 21, 2012 at 09:44 AM
In answer to your latest version, the reason that you see nothing is because you are assigning the arrays too early. Take a look at the following snippet as an example, it's written in C# but only the comments are really important:
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class MeshDemo : MonoBehaviour {
void Awake() {
CreateMesh();
}
void CreateMesh() {
MeshFilter filter = GetComponent<MeshFilter>();
Mesh mesh = new Mesh();
// Simple demo mesh
Vector3[] vx = new Vector3[4];
Vector2[] uv = new Vector2[4];
Vector3[] n = new Vector3[4];
int[] ix = new int[6];
// If you want to never see the mesh then assign properties here and comment out the lower assignments.
/*
mesh.vertices = vx;
mesh.uv = uv;
mesh.normals = n;
mesh.triangles = ix;
*/
for (int i=0; i<4; ++i){
vx[i].x = 0 != (i&1) ? -1f : 1f;
vx[i].z = 0 != (i&2) ? -1f : 1f;
n[i] = Vector3.up;
uv[i].x = 0 != (i&1) ? 0f : 1f;
uv[i].y = 0 != (i&2) ? 0f : 1f;
}
ix[0] = 0; ix[1] = 2; ix[2] = 1;
ix[3] = 1; ix[4] = 2; ix[5] = 3;
// Now that all the values are defined we can assign them to the mesh,
mesh.vertices = vx;
mesh.uv = uv;
mesh.normals = n;
mesh.triangles = ix;
Debug.Log("Mesh ready");
filter.mesh = mesh;
}
}
I'm sorry but I really don't understand. In your example you create temporary arrays, use them in your calculations, then assign them to the mesh. This is exactly what I am doing. So I really fail to see how I am duplicating or using redundant assignments. This whole thing is getting further from my actual problem (and actual question), and I have just simply been left here confused.
I've seen people become confused by this point before. In the code sample above, we have temporary references to arrays (vx,ux, etc.) the arrays themselves are not temporary (or at least they are of the same scope as mesh).
In the block that was commented out, we trigger the internal processes of $$anonymous$$esh too early by making the various property assignments; each array entry will still have its default values at this point. In particular, since the index array will be full of zeroes, the mesh builder will construct a mesh based on a single point (or possibly even optimise it out).