/**
   * This method retuns the bone tracks for animation for blender version 2.50 and higher.
   *
   * @param actionStructure the structure containing the tracks
   * @param blenderContext the blender context
   * @return a list of tracks for the specified animation
   * @throws BlenderFileException an exception is thrown when there are problems with the blend file
   */
  private BlenderAction getTracks250(Structure actionStructure, BlenderContext blenderContext)
      throws BlenderFileException {
    LOGGER.log(Level.FINE, "Getting tracks!");
    Structure groups = (Structure) actionStructure.getFieldValue("groups");
    List<Structure> actionGroups = groups.evaluateListBase(); // bActionGroup
    BlenderAction blenderAction =
        new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
    int lastFrame = 1;
    for (Structure actionGroup : actionGroups) {
      String name = actionGroup.getFieldValue("name").toString();
      List<Structure> channels =
          ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase();
      BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
      int channelCounter = 0;
      for (Structure c : channels) {
        int type = this.getCurveType(c, blenderContext);
        Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");
        List<Structure> bezTriples = pBezTriple.fetchData();
        bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
      }

      Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
      lastFrame = Math.max(lastFrame, ipo.getLastFrame());
      blenderAction.featuresTracks.put(name, ipo);
    }
    blenderAction.stopFrame = lastFrame;
    return blenderAction;
  }
 /**
  * Loads all animations that are stored in the blender file. The animations are not yet applied to
  * the scene features. This should be called before objects are loaded.
  *
  * @throws BlenderFileException an exception is thrown when problems with blender file reading
  *     occur
  */
 public void loadAnimations() throws BlenderFileException {
   LOGGER.info("Loading animations that will be later applied to scene features.");
   List<FileBlockHeader> actionHeaders =
       blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
   if (actionHeaders != null) {
     for (FileBlockHeader header : actionHeaders) {
       Structure actionStructure = header.getStructure(blenderContext);
       LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName());
       blenderContext.addAction(this.getTracks(actionStructure, blenderContext));
     }
   }
 }
 /**
  * This method retuns the bone tracks for animation for blender version 2.49 (and probably several
  * lower versions too).
  *
  * @param actionStructure the structure containing the tracks
  * @param blenderContext the blender context
  * @return a list of tracks for the specified animation
  * @throws BlenderFileException an exception is thrown when there are problems with the blend file
  */
 private BlenderAction getTracks249(Structure actionStructure, BlenderContext blenderContext)
     throws BlenderFileException {
   LOGGER.log(Level.FINE, "Getting tracks!");
   Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
   List<Structure> actionChannels = chanbase.evaluateListBase(); // bActionChannel
   BlenderAction blenderAction =
       new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
   int lastFrame = 1;
   for (Structure bActionChannel : actionChannels) {
     String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
     Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");
     if (!p.isNull()) {
       Structure ipoStructure = p.fetchData().get(0);
       Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext);
       if (ipo != null) { // this can happen when ipo with no curves appear in blender file
         lastFrame = Math.max(lastFrame, ipo.getLastFrame());
         blenderAction.featuresTracks.put(animatedFeatureName, ipo);
       }
     }
   }
   blenderAction.stopFrame = lastFrame;
   return blenderAction;
 }
Example #4
0
  /**
   * This method reads constraints for for the given structure. The constraints are loaded only once
   * for object/bone.
   *
   * @param objectStructure the structure we read constraint's for
   * @param blenderContext the blender context
   * @throws BlenderFileException
   */
  public void loadConstraints(Structure objectStructure, BlenderContext blenderContext)
      throws BlenderFileException {
    LOGGER.fine("Loading constraints.");
    // reading influence ipos for the constraints
    IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
    Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
    Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
    if (pActions.isNotNull()) {
      List<Structure> actions = pActions.fetchData(blenderContext.getInputStream());
      for (Structure action : actions) {
        Structure chanbase = (Structure) action.getFieldValue("chanbase");
        List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);
        for (Structure actionChannel : actionChannels) {
          Map<String, Ipo> ipos = new HashMap<String, Ipo>();
          Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");
          List<Structure> constraintChannels = constChannels.evaluateListBase(blenderContext);
          for (Structure constraintChannel : constraintChannels) {
            Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
            if (pIpo.isNotNull()) {
              String constraintName = constraintChannel.getFieldValue("name").toString();
              Ipo ipo =
                  ipoHelper.fromIpoStructure(
                      pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext);
              ipos.put(constraintName, ipo);
            }
          }
          String actionName = actionChannel.getFieldValue("name").toString();
          constraintsIpos.put(actionName, ipos);
        }
      }
    }

    // loading constraints connected with the object's bones
    Pointer pPose = (Pointer) objectStructure.getFieldValue("pose");
    if (pPose.isNotNull()) {
      List<Structure> poseChannels =
          ((Structure)
                  pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase"))
              .evaluateListBase(blenderContext);
      for (Structure poseChannel : poseChannels) {
        List<Constraint> constraintsList = new ArrayList<Constraint>();
        Long boneOMA =
            Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());

        // the name is read directly from structure because bone might not yet be loaded
        String name =
            blenderContext
                .getFileBlock(boneOMA)
                .getStructure(blenderContext)
                .getFieldValue("name")
                .toString();
        List<Structure> constraints =
            ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext);
        for (Structure constraint : constraints) {
          String constraintName = constraint.getFieldValue("name").toString();
          Map<String, Ipo> ipoMap = constraintsIpos.get(name);
          Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
          if (ipo == null) {
            float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
            ipo = ipoHelper.fromValue(enforce);
          }
          constraintsList.add(this.createConstraint(constraint, boneOMA, ipo, blenderContext));
        }
        blenderContext.addConstraints(boneOMA, constraintsList);
      }
    }

    // loading constraints connected with the object itself
    List<Structure> constraints =
        ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext);
    List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size());

    for (Structure constraint : constraints) {
      String constraintName = constraint.getFieldValue("name").toString();
      String objectName = objectStructure.getName();

      Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName);
      Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null;
      if (ipo == null) {
        float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
        ipo = ipoHelper.fromValue(enforce);
      }
      constraintsList.add(
          this.createConstraint(
              constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
    }
    blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
  }
  /**
   * The method loads library of a given ID from linked blender file.
   *
   * @param id the ID of the linked feature (it contains its name and blender path)
   * @return loaded feature or null if none was found
   * @throws BlenderFileException and exception is throw when problems with reading a blend file
   *     occur
   */
  @SuppressWarnings("unchecked")
  protected Object loadLibrary(Structure id) throws BlenderFileException {
    Pointer pLib = (Pointer) id.getFieldValue("lib");
    if (pLib.isNotNull()) {
      String fullName = id.getFieldValue("name").toString(); // we need full name with the prefix
      String nameOfFeatureToLoad = id.getName();
      Structure library = pLib.fetchData().get(0);
      String path = library.getFieldValue("filepath").toString();

      if (!blenderContext.getLinkedFeatures().keySet().contains(path)) {
        File file = new File(path);
        List<String> pathsToCheck = new ArrayList<String>();
        String currentPath = file.getName();
        do {
          pathsToCheck.add(currentPath);
          file = file.getParentFile();
          if (file != null) {
            currentPath = file.getName() + '/' + currentPath;
          }
        } while (file != null);

        Spatial loadedAsset = null;
        BlenderKey blenderKey = null;
        for (String p : pathsToCheck) {
          blenderKey = new BlenderKey(p);
          blenderKey.setLoadUnlinkedAssets(true);
          try {
            loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey);
            break; // break if no exception was thrown
          } catch (AssetNotFoundException e) {
            LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", p);
          }
        }

        if (loadedAsset != null) {
          Map<String, Map<String, Object>> linkedData = loadedAsset.getUserData("linkedData");
          for (Entry<String, Map<String, Object>> entry : linkedData.entrySet()) {
            String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey();

            List<Node> scenes = (List<Node>) entry.getValue().get("scenes");
            for (Node scene : scenes) {
              blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene);
            }
            List<Node> objects = (List<Node>) entry.getValue().get("objects");
            for (Node object : objects) {
              blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object);
            }
            List<TemporalMesh> meshes = (List<TemporalMesh>) entry.getValue().get("meshes");
            for (TemporalMesh mesh : meshes) {
              blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh);
            }
            List<MaterialContext> materials =
                (List<MaterialContext>) entry.getValue().get("materials");
            for (MaterialContext materialContext : materials) {
              blenderContext.addLinkedFeature(
                  linkedDataFilePath, "MA" + materialContext.getName(), materialContext);
            }
            List<Texture> textures = (List<Texture>) entry.getValue().get("textures");
            for (Texture texture : textures) {
              blenderContext.addLinkedFeature(
                  linkedDataFilePath, "TE" + texture.getName(), texture);
            }
            List<Texture> images = (List<Texture>) entry.getValue().get("images");
            for (Texture image : images) {
              blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image);
            }
            List<Animation> animations = (List<Animation>) entry.getValue().get("animations");
            for (Animation animation : animations) {
              blenderContext.addLinkedFeature(
                  linkedDataFilePath, "AC" + animation.getName(), animation);
            }
            List<Camera> cameras = (List<Camera>) entry.getValue().get("cameras");
            for (Camera camera : cameras) {
              blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera);
            }
            List<Light> lights = (List<Light>) entry.getValue().get("lights");
            for (Light light : lights) {
              blenderContext.addLinkedFeature(linkedDataFilePath, "LA" + light.getName(), light);
            }
            Spatial sky = (Spatial) entry.getValue().get("sky");
            if (sky != null) {
              blenderContext.addLinkedFeature(linkedDataFilePath, sky.getName(), sky);
            }
            List<Filter> filters = (List<Filter>) entry.getValue().get("filters");
            for (Filter filter : filters) {
              blenderContext.addLinkedFeature(linkedDataFilePath, filter.getName(), filter);
            }
          }
        } else {
          LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path);
        }
      }

      Object result = blenderContext.getLinkedFeature(path, fullName);
      if (result == null) {
        LOGGER.log(
            Level.WARNING,
            "Could NOT find asset named {0} in the library of path: {1}.",
            new Object[] {nameOfFeatureToLoad, path});
      } else {
        blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id);
        blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
      }
      return result;
    } else {
      LOGGER.warning("Library link points to nothing!");
    }
    return null;
  }
  /**
   * This constructor reads animation data from the object structore. The stored data is the
   * AnimData and additional data is armature's OMA.
   *
   * @param objectStructure the structure of the object
   * @param modifierStructure the structure of the modifier
   * @param blenderContext the blender context
   * @throws BlenderFileException this exception is thrown when the blender file is somehow
   *     corrupted
   */
  public ArmatureModifier(
      Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext)
      throws BlenderFileException {
    if (this.validate(modifierStructure, blenderContext)) {
      Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
      if (pArmatureObject.isNotNull()) {
        ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
        ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);

        Structure armatureObject =
            pArmatureObject.fetchData(blenderContext.getInputStream()).get(0);
        this.armatureObjectOMA = armatureObject.getOldMemoryAddress();

        // read skeleton
        // changing bones matrices so that they fit the current object
        Structure armatureStructure =
            ((Pointer) armatureObject.getFieldValue("data"))
                .fetchData(blenderContext.getInputStream())
                .get(0);
        Structure bonebase = (Structure) armatureStructure.getFieldValue("bonebase");
        List<Structure> bonesStructures = bonebase.evaluateListBase(blenderContext);
        for (Structure boneStructure : bonesStructures) {
          BoneTransformationData rootBoneTransformationData =
              armatureHelper.readBoneAndItsChildren(boneStructure, null, blenderContext);
          armatureHelper.addBoneDataRoot(rootBoneTransformationData);
        }
        Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject);
        Matrix4f inverseMeshObjectMatrix =
            objectHelper.getTransformationMatrix(objectStructure).invert();
        Matrix4f additionalRootBoneTransformation =
            inverseMeshObjectMatrix.multLocal(armatureObjectMatrix);
        Bone[] bones =
            armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation);

        // read mesh indexes
        Structure meshStructure =
            ((Pointer) objectStructure.getFieldValue("data"))
                .fetchData(blenderContext.getInputStream())
                .get(0);
        this.meshOMA = meshStructure.getOldMemoryAddress();
        this.readVerticesWeightsData(objectStructure, meshStructure, blenderContext);

        // read animations
        ArrayList<Animation> animations = new ArrayList<Animation>();
        List<FileBlockHeader> actionHeaders =
            blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
        if (actionHeaders != null) { // it may happen that the model has armature with no actions
          for (FileBlockHeader header : actionHeaders) {
            Structure actionStructure = header.getStructure(blenderContext);
            String actionName = actionStructure.getName();

            BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, blenderContext);
            // determining the animation  time
            float maximumTrackLength = 0;
            for (BoneTrack track : tracks) {
              float length = track.getLength();
              if (length > maximumTrackLength) {
                maximumTrackLength = length;
              }
            }

            Animation boneAnimation = new Animation(actionName, maximumTrackLength);
            boneAnimation.setTracks(tracks);
            animations.add(boneAnimation);
          }
        }
        animData = new AnimData(new Skeleton(bones), animations);
      }
    }
  }
  /**
   * This method returns an array of size 2. The first element is a vertex buffer holding bone
   * weights for every vertex in the model. The second element is a vertex buffer holding bone
   * indices for vertices (the indices of bones the vertices are assigned to).
   *
   * @param meshStructure the mesh structure object
   * @param vertexListSize a number of vertices in the model
   * @param bonesGroups this is an output parameter, it should be a one-sized array; the maximum
   *     amount of weights per vertex (up to MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
   * @param vertexReferenceMap this reference map allows to map the original vertices read from
   *     blender to vertices that are really in the model; one vertex may appear several times in
   *     the result model
   * @param groupToBoneIndexMap this object maps the group index (to which a vertices in blender
   *     belong) to bone index of the model
   * @param blenderContext the blender context
   * @return arrays of vertices weights and their bone indices and (as an output parameter) the
   *     maximum amount of weights for a vertex
   * @throws BlenderFileException this exception is thrown when the blend file structure is somehow
   *     invalid or corrupted
   */
  private VertexBuffer[] getBoneWeightAndIndexBuffer(
      Structure meshStructure,
      int vertexListSize,
      int[] bonesGroups,
      Map<Integer, List<Integer>> vertexReferenceMap,
      Map<Integer, Integer> groupToBoneIndexMap,
      BlenderContext blenderContext)
      throws BlenderFileException {
    Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert"); // dvert = DeformVERTices
    FloatBuffer weightsFloatData =
        BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
    ByteBuffer indicesData =
        BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
    if (pDvert.isNotNull()) { // assigning weights and bone indices
      List<Structure> dverts =
          pDvert.fetchData(
              blenderContext.getInputStream()); // dverts.size() == verticesAmount (one dvert per
      // vertex in blender)
      int vertexIndex = 0;
      for (Structure dvert : dverts) {
        int totweight =
            ((Number) dvert.getFieldValue("totweight"))
                .intValue(); // total amount of weights assignet to the vertex
        // (max. 4 in JME)
        Pointer pDW = (Pointer) dvert.getFieldValue("dw");
        List<Integer> vertexIndices =
            vertexReferenceMap.get(
                Integer.valueOf(vertexIndex)); // we fetch the referenced vertices here
        if (totweight > 0
            && pDW.isNotNull()
            && groupToBoneIndexMap
                != null) { // pDW should never be null here, but I check it just in case :)
          int weightIndex = 0;
          List<Structure> dw = pDW.fetchData(blenderContext.getInputStream());
          for (Structure deformWeight : dw) {
            Integer boneIndex =
                groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());

            // Remove this code if 4 weights limitation is removed
            if (weightIndex == 4) {
              LOGGER.log(
                  Level.WARNING,
                  "{0} has more than 4 weight on bone index {1}",
                  new Object[] {meshStructure.getName(), boneIndex});
              break;
            }

            if (boneIndex
                != null) { // null here means that we came accross group that has no bone attached
                           // to
              float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
              if (weight == 0.0f) {
                weight = 1;
                boneIndex = Integer.valueOf(0);
              }
              // we apply the weight to all referenced vertices
              for (Integer index : vertexIndices) {
                weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
                indicesData.put(
                    index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
              }
            }
            ++weightIndex;
          }
        } else {
          for (Integer index : vertexIndices) {
            weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
            indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
          }
        }
        ++vertexIndex;
      }
    } else {
      // always bind all vertices to 0-indexed bone
      // this bone makes the model look normally if vertices have no bone assigned
      // and it is used in object animation, so if we come accross object animation
      // we can use the 0-indexed bone for this
      for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
        // we apply the weight to all referenced vertices
        for (Integer index : vertexIndexList) {
          weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
          indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
        }
      }
    }

    bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);
    VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
    verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);

    VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
    verticesWeightsIndices.setupData(
        Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
    return new VertexBuffer[] {verticesWeights, verticesWeightsIndices};
  }