parse method

  1. @override
dynamic parse(
  1. dynamic json, [
  2. String? path,
  3. Function? onLoad,
  4. Function? onError,
])
override

Implementation

@override
parse(json, [String? path, Function? onLoad, Function? onError]) async {
  var state = ParserState();

  if (json.indexOf('\r\n') != -1) {
    // This is faster than String.split with regex that splits on both
    json = json.replaceAll(RegExp("\r\n", multiLine: true), '\n');
  }

  if (json.indexOf('\\\n') != -1) {
    // join lines separated by a line continuation character (\)
    json = json.replaceAll(RegExp("\\\n"), '');
  }

  var lines = json.split('\n');
  var line = '', lineFirstChar = '';
  var lineLength = 0;
  var result = [];

  // Faster to just trim left side of the line. Use if available.

  for (var i = 0, l = lines.length; i < l; i++) {
    line = lines[i];

    line = line.trimLeft();

    // print("i: ${i} line: ${line} ");

    lineLength = line.length;

    if (lineLength == 0) continue;

    lineFirstChar = line[0];

    // @todo invoke passed in handler if any
    if (lineFirstChar == '#') continue;

    if (lineFirstChar == 'v') {
      var data = line.split(RegExp(r"\s+"));

      switch (data[0]) {
        case 'v':
          state.vertices.addAll([parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3])]);
          if (data.length >= 7) {
            state.colors.addAll([parseFloat(data[4]), parseFloat(data[5]), parseFloat(data[6])]);
          } else {
            // if no colors are defined, add placeholders so color and vertex indices match
            state.colors.addAll([]);
          }

          break;
        case 'vn':
          state.normals.addAll([parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3])]);
          break;
        case 'vt':
          state.uvs.addAll([parseFloat(data[1]), parseFloat(data[2])]);
          break;
      }
    } else if (lineFirstChar == 'f') {
      var lineData = line.substring(1).trim();
      var vertexData = lineData.split(RegExp(r"\s+"));
      List<List> faceVertices = [];

      // Parse the face vertex data into an easy to work with format

      // print(" lineFirstChar is f .................. ");
      // print(vertexData);

      for (var j = 0, jl = vertexData.length; j < jl; j++) {
        var vertex = vertexData[j];

        if (vertex.isNotEmpty) {
          var vertexParts = vertex.split('/');
          faceVertices.add(vertexParts);
        }
      }

      // Draw an edge between the first vertex and all subsequent vertices to form an n-gon

      var v1 = faceVertices[0];

      for (var j = 1, jl = faceVertices.length - 1; j < jl; j++) {
        var v2 = faceVertices[j];
        var v3 = faceVertices[j + 1];

        state.addFace(
            v1[0],
            v2[0],
            v3[0],
            v1.length > 1 ? v1[1] : null,
            v2.length > 1 ? v2[1] : null,
            v3.length > 1 ? v3[1] : null,
            v1.length > 2 ? v1[2] : null,
            v2.length > 2 ? v2[2] : null,
            v3.length > 2 ? v3[2] : null);
      }
    } else if (lineFirstChar == 'l') {
      var lineParts = line.substring(1).trim().split(' ');
      var lineVertices = [];
      var lineUVs = [];

      if (!line.contains('/')) {
        lineVertices = lineParts;
      } else {
        for (var li = 0, llen = lineParts.length; li < llen; li++) {
          var parts = lineParts[li].split('/');

          if (parts[0] != '') lineVertices.add(parts[0]);
          if (parts[1] != '') lineUVs.add(parts[1]);
        }
      }

      state.addLineGeometry(lineVertices, lineUVs);
    } else if (lineFirstChar == 'p') {
      var lineData = line.substring(1).trim();
      var pointData = lineData.split(' ');

      state.addPointGeometry(pointData);
    } else if (_objectPattern.hasMatch(line)) {
      result = _objectPattern.allMatches(line).toList();

      // o object_name
      // or
      // g group_name

      // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
      // var name = result[ 0 ].substr( 1 ).trim();
      var name = ' ${result[0].group(0).substring(1).trim()}'.substring(1);

      state.startObject(name, null);
    } else if (_materialUsePattern.hasMatch(line)) {
      // material

      state.object!.startMaterial(line.substring(7).trim(), state.materialLibraries);
    } else if (_materialLibraryPattern.hasMatch(line)) {
      // mtl file

      state.materialLibraries.add(line.substring(7).trim());
    } else if (_mapUsePattern.hasMatch(line)) {
      // the line is parsed but ignored since the loader assumes textures are defined MTL files
      // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)

      print('THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.');
    } else if (lineFirstChar == 's') {
      result = line.split(' ');

      // smooth shading

      // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
      // but does not define a usemtl for each face set.
      // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
      // This requires some care to not create extra material on each smooth value for "normal" obj files.
      // where explicit usemtl defines geometry groups.
      // Example asset: examples/models/obj/cerberus/Cerberus.obj

      /*
					 * http://paulbourke.net/dataformats/obj/
					 * or
					 * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
					 *
					 * From chapter "Grouping" Syntax explanation "s group_number":
					 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
					 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
					 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
					 * than 0."
					 */
      if (result.length > 1) {
        var value = result[1].trim().toLowerCase();
        state.object!.smooth = (value != '0' && value != 'off');
      } else {
        // ZBrush can produce "s" lines #11707
        state.object!.smooth = true;
      }

      var material = state.object!.currentMaterial();
      if (material != null) material.smooth = state.object!.smooth;
    } else {
      // Handle null terminated files without exception
      if (line == '0') continue;

      print('THREE.OBJLoader: Unexpected line: "$line"');
    }
  }

  state.finalize();

  var container = Group();
  // container.materialLibraries = [].concat( state.materialLibraries );

  var hasPrimitives = !(state.objects.length == 1 && state.objects[0].geometry["vertices"].length == 0);

  if (hasPrimitives == true) {
    for (var i = 0, l = state.objects.length; i < l; i++) {
      var object = state.objects[i];
      var geometry = object.geometry;
      var materials = object.materials;
      var isLine = (geometry["type"] == 'Line');
      var isPoints = (geometry["type"] == 'Points');
      var hasVertexColors = false;

      // Skip o/g line declarations that did not follow with any faces
      if (geometry["vertices"].length == 0) continue;

      var buffergeometry = BufferGeometry();

      buffergeometry.setAttribute(
          'position', Float32BufferAttribute(Float32Array.fromList(List<double>.from(geometry["vertices"])), 3));

      if (geometry["normals"].length > 0) {
        buffergeometry.setAttribute(
            'normal', Float32BufferAttribute(Float32Array.fromList(List<double>.from(geometry["normals"])), 3));
      }

      if (geometry["colors"].length > 0) {
        hasVertexColors = true;
        buffergeometry.setAttribute(
            'color', Float32BufferAttribute(Float32Array.fromList(List<double>.from(geometry["colors"])), 3));
      }

      if (geometry["hasUVIndices"] == true) {
        buffergeometry.setAttribute(
            'uv', Float32BufferAttribute(Float32Array.fromList(List<double>.from(geometry["uvs"])), 2));
      }

      // Create materials

      var createdMaterials = [];

      for (var mi = 0, miLen = materials.length; mi < miLen; mi++) {
        var sourceMaterial = materials[mi];
        var materialHash = sourceMaterial.name + '_' + "${sourceMaterial.smooth}" + '_' + "$hasVertexColors";
        var material = state.materials[materialHash];

        if (this.materials != null) {
          material = await this.materials!.create(sourceMaterial.name);

          // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
          if (isLine && material && material is! LineBasicMaterial) {
            var materialLine = LineBasicMaterial({});
            materialLine.copy(material);
            // Material.prototype.copy.call( materialLine, material );
            materialLine.color.copy(material.color);
            material = materialLine;
          } else if (isPoints && material && material is! PointsMaterial) {
            var materialPoints = PointsMaterial({"size": 10, "sizeAttenuation": false});
            // Material.prototype.copy.call( materialPoints, material );
            materialPoints.copy(material);
            materialPoints.color.copy(material.color);
            materialPoints.map = material.map;
            material = materialPoints;
          }
        }

        if (material == null) {
          if (isLine) {
            material = LineBasicMaterial({});
          } else if (isPoints) {
            material = PointsMaterial({"size": 1, "sizeAttenuation": false});
          } else {
            material = MeshPhongMaterial();
          }

          material.name = sourceMaterial.name;
          material.flatShading = sourceMaterial.smooth ? false : true;
          material.vertexColors = hasVertexColors;

          state.materials[materialHash] = material;
        }

        createdMaterials.add(material);
      }

      // Create mesh
      var mesh;

      if (createdMaterials.length > 1) {
        for (var mi = 0, miLen = materials.length; mi < miLen; mi++) {
          var sourceMaterial = materials[mi];
          buffergeometry.addGroup(sourceMaterial.groupStart.toInt(), sourceMaterial.groupCount.toInt(), mi);
        }

        if (isLine) {
          mesh = LineSegments(buffergeometry, createdMaterials);
        } else if (isPoints) {
          mesh = Points(buffergeometry, createdMaterials);
        } else {
          mesh = Mesh(buffergeometry, createdMaterials);
        }
      } else {
        if (isLine) {
          mesh = LineSegments(buffergeometry, createdMaterials[0]);
        } else if (isPoints) {
          mesh = Points(buffergeometry, createdMaterials[0]);
        } else {
          mesh = Mesh(buffergeometry, createdMaterials[0]);
        }
      }

      mesh.name = object.name;

      container.add(mesh);
    }
  } else {
    // if there is only the default parser state object with no geometry data, interpret data as point cloud

    if (state.vertices.isNotEmpty) {
      var material = PointsMaterial({"size": 1, "sizeAttenuation": false});

      var buffergeometry = BufferGeometry();

      buffergeometry.setAttribute('position', Float32BufferAttribute(Float32Array.fromList(state.vertices), 3));

      if (state.colors.isNotEmpty) {
        buffergeometry.setAttribute('color', Float32BufferAttribute(Float32Array.fromList(state.colors), 3));
        material.vertexColors = true;
      }

      var points = Points(buffergeometry, material);
      container.add(points);
    }
  }

  return container;
}