/* */ if (script.isPreview()) return; scene = script.getScene(); // The following line gets the object (which must be a triangle mesh or spine mesh) to add fur to. info = scene.getObject("furry"); // The following line gets the fur texture. fur = scene.getTexture("fur"); // This sets the number of shells to use. More shells will give better looking fur, but also take // more time and memory to render. When rendering, be sure to set the maximum ray tree depth (in // the advanced raFur.bshytracer options window) to be greater than the number of shells. Otherwise, the // raytracer will not get through all of the shells. shells = 10; // This is a force which pulls on the hair, usually downward. It can also be animated to simulate // wind or similar effects. gravity = new Vec3(0.0, -0.4, 0.0); script.getCoordinates().toLocal().transformDirection(gravity); // Everything from here on you should not need to modify, unless you are making significant changes // to the way the script works. // Check various inputs to make sure they're ok. if (info == null) { print("Could not find object to apply fur to"); return; } if (fur == null) { print("Could not find fur texture"); return; } // Find the mapping used for the fur texture. map = null; obj = info.getDistortedObject(0.1); if (obj instanceof artofillusion.animation.Actor) obj = obj.getObject(); if (!(obj instanceof Mesh)) { print("Fur can only be applied to a mesh"); return; } if (obj.getTexture() == fur) map = obj.getTextureMapping(); else if (obj.getTextureMapping() instanceof LayeredMapping) { layered = obj.getTextureMapping(); for (i = 0; i < layered.getNumLayers(); i++) if (layered.getLayer(i) == fur) { map = layered.getLayerMapping(i); break; } } if (map == null) { print("The fur texture has not been assigned to the object."); return; } // This function finds the texture parameter with a given name. findParam(String name) { params = obj.getParameters(); for (i = 0; i < params.length; i++) if (params[i].owner == fur && params[i].name.equals(name)) return params[i]; return null; } // This function finds the value of a texture parameter at each vertex. findParamValues(String name) { param = findParam(name); if (param == null) return param; value = obj.getParameterValue(param); if (value instanceof VertexParameterValue) return value.getValue(); avg = value.getAverageValue(); vertValue = new double [obj.getVertices().length]; for (int j = 0; j < vertValue.length; j++) vertValue[j] = avg; return vertValue; } // Look up the values of the thickness and length parameters. thicknessParam = findParam("thickness"); if (thicknessParam == null) { print("Could not find texture parameter 'thickness'"); return; } furThickness = obj.getParameterValue(thicknessParam); furLength = findParamValues("length"); if (furLength == null) { print("Could not find texture parameter 'length'"); return; } // This is the main loop that generates the shells. heightScale = 1.0/shells; for (i = 0; i < shells; i++) { newobj = obj.duplicate(); norm = newobj.getNormals(); vert = newobj.getVertices(); for (j = 0; j < vert.length; j++) { disp = norm[j].plus(gravity); disp.normalize(); disp.scale(furLength[j]*heightScale); vert[j].r.add(disp); } newobj.setTexture(fur, map.duplicate()); // Taper the ends of the hairs. if (i > shells*0.75) { thicknessScale = 4.0*(shells-i)/shells; if (furThickness instanceof VertexParameterValue || furThickens instanceof FaceParameterValue) { newThickness = furThickness.duplicate(); val = newThickness.getValue(); for (j = 0; j < val.length; j++) val[j] *= thicknessScale; newThickness.setValue(val); newobj.setParameterValue(thicknessParam, newThickness); } else newobj.setParameterValue(thicknessParam, new ConstantParameterValue(thicknessScale*furThickness.getAverageValue())); } script.addObject(newobj, info.coords.duplicate()); obj = newobj; }