diff --git a/resources.qrc b/resources.qrc
index 2f8d5a95..bb041a06 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -13,6 +13,7 @@
resources/model-dog-head.ds3
resources/model-meerkat.ds3
resources/model-mosquito.ds3
+ resources/model-procedural-tree.ds3
resources/triangle.png
resources/quad.png
resources/pentagon.png
diff --git a/resources/model-procedural-tree.ds3 b/resources/model-procedural-tree.ds3
new file mode 100644
index 00000000..3e3c29a9
--- /dev/null
+++ b/resources/model-procedural-tree.ds3
@@ -0,0 +1,881 @@
+DUST3D 1.0 xml 0000000257
+
+
+
+
+
+
+
+
+var SUBDIVED = document.createCheckInput("Subdived", false);
+var MAX_RADIUS_OFFSET = document.createFloatInput("Max Radius Offset", 0.10, -1.0, 1.0);
+var MAX_ORIGIN_OFFSET = document.createFloatInput("Max Origin Offset", 0.47, -1.0, 1.0);
+var TREE_HEIGHT = document.createFloatInput("Height", 0.85, 0.1, 1.0);
+var TRUNK_RADIUS = document.createFloatInput("Trunk Radius", 0.02, 0.01, 0.2);
+var TRUNK_SEGMENTS = document.createIntInput("Trunk Segments", 15, 2, 30);
+var TRUNK_COLOR = document.createColorInput("Trunk Color", "#939297");
+var BRANCH_MIN_ANGLE = document.createFloatInput("Branch Min Angle", 10.72, 8, 30);
+var BRANCH_MAX_ANGLE = document.createFloatInput("Branch Max Angle", 58.81, 50, 75);
+var MAX_BRANCHES_PER_GROWTH = document.createIntInput("Max Branches", 7, 1, 7);
+var BRANCH_LENGTH_MIN_RATIO = document.createFloatInput("Branch Length Min Ratio", 0.24, 0.2, 0.9);
+var BRANCH_LENGTH_MAX_RATIO = document.createFloatInput("Branch Length Max Ratio", 0.78, 0.2, 0.9);
+var LEAF_MIN_LENGTH = document.createFloatInput("Leaf Min Length", 0.05, 0.005, 0.1);
+var LEAF_MAX_LENGTH = document.createFloatInput("Leaf Max Length", 0.08, 0.005, 0.2);
+var LEAF_SEGMENTS = document.createIntInput("Leaf Segments", 5, 3, 6);
+var LEAF_COLOR = document.createColorInput("Leaf Color", "#58834b");
+
+var X = new THREE.Vector3( 1, 0, 0 );
+var Y = new THREE.Vector3( 0, 1, 0 );
+
+function createNode(part, origin, radius)
+{
+ var node = document.createNode(part);
+ document.setAttribute(node, "x", origin.getComponent(0));
+ document.setAttribute(node, "y", origin.getComponent(1));
+ document.setAttribute(node, "z", origin.getComponent(2));
+ document.setAttribute(node, "radius", radius);
+ return node;
+}
+
+THREE.Vector3.prototype.random = function (maxOffset)
+{
+ for (var i = 0; i < 3; ++i)
+ this.setComponent(i, this.getComponent(i) + maxOffset * Math.random());
+ return this;
+};
+
+function randInRange(min, max)
+{
+ if (min > max) {
+ var tmp = min;
+ min = max;
+ max = tmp;
+ }
+ return (min + (max - min) * Math.random());
+}
+
+function createLeafCutFace()
+{
+ var component = document.createComponent();
+ var part = document.createPart(component);
+ document.setAttribute(part, "target", "CutFace");
+ var predefinesCutFace = [
+ {radius:"0.005", x:"0.314637", y:"0.336525", z:"0.713406"},
+ {radius:"0.005", x:"0.343365", y:"0.340629", z:"0.713406"},
+ {radius:"0.005", x:"0.377565", y:"0.340629", z:"0.713406"},
+ {radius:"0.0159439", x:"0.402189", y:"0.341997", z:"0.713406"},
+ {radius:"0.005", x:"0.440492", y:"0.329685", z:"0.713406"},
+ {radius:"0.005", x:"0.473324", y:"0.317373", z:"0.713406"},
+ ];
+ var previousNode = undefined;
+ for (var i = 0; i < predefinesCutFace.length; ++i) {
+ var node = createNode(part,
+ new THREE.Vector3(parseFloat(predefinesCutFace[i]["x"]),
+ parseFloat(predefinesCutFace[i]["y"]),
+ parseFloat(predefinesCutFace[i]["z"])),
+ parseFloat(predefinesCutFace[i]["radius"]));
+ if (undefined != previousNode)
+ document.connect(previousNode, node);
+ previousNode = node;
+ }
+ return part;
+}
+
+function getRandChildDirection(parentDirection)
+{
+ var rotationAxis = parentDirection.clone().cross(X);
+ var degree = randInRange(BRANCH_MIN_ANGLE, BRANCH_MAX_ANGLE);
+ var branchRotateQuaternion = new THREE.Quaternion();
+ branchRotateQuaternion.setFromAxisAngle(rotationAxis, degree * (Math.PI / 180));
+ var rotatedDirection = parentDirection.clone().applyQuaternion(branchRotateQuaternion);
+
+ var distributeQuaternion = new THREE.Quaternion();
+ distributeQuaternion.setFromAxisAngle(Y, 360 * Math.random() * (Math.PI / 180));
+ rotatedDirection = rotatedDirection.applyQuaternion(distributeQuaternion);
+
+ return rotatedDirection;
+}
+
+var CUTFACE_PART_ID = document.attribute(createLeafCutFace(), "id");
+
+function createLeaf(leafRootPosition, parentDirection)
+{
+ var direction = getRandChildDirection(parentDirection);
+ var component = document.createComponent();
+ var part = document.createPart(component);
+ document.setAttribute(part, "color", LEAF_COLOR);
+ document.setAttribute(part, "cutFace", CUTFACE_PART_ID);
+ var length = randInRange(LEAF_MIN_LENGTH, LEAF_MAX_LENGTH);
+ var segments = LEAF_SEGMENTS;
+ var maxRadius = length / 3;
+ var toPosition = leafRootPosition.clone().add(direction.clone().multiplyScalar(length));
+ var previousNode = undefined;
+ for (var i = 0; i < segments; ++i) {
+ var alpha = (i + 0.0) / segments;
+ var origin = leafRootPosition.clone().lerp(toPosition, alpha);
+ var radiusFactor = 1.0 - 2 * Math.abs(alpha - 0.3);
+ var radius = maxRadius * radiusFactor;
+ var node = createNode(part, origin, radius);
+ if (undefined != previousNode)
+ document.connect(previousNode, node);
+ previousNode = node;
+ }
+}
+
+function createBranch(branchRootPosition, radius, length, segments, parentDirection, maxRadius)
+{
+ var rotatedDirection = getRandChildDirection(parentDirection);
+ var branchEndPosition = branchRootPosition.clone().add(rotatedDirection.multiplyScalar(length));
+ createTrunk(branchRootPosition, radius, branchEndPosition, segments, maxRadius);
+}
+
+function shouldGrowBranch(alpha, alreadyGrowedNum)
+{
+ var factor = alpha * Math.random();
+ if (factor > 0.5 && factor < 0.8) {
+ if (alreadyGrowedNum > 0)
+ return true;
+ if (Math.random() > 0.5)
+ return true;
+ }
+ return false;
+}
+
+function shouldGrowLeaf(alreadyGrowedNum)
+{
+ var factor = Math.random();
+ if (factor > 0.5) {
+ if (alreadyGrowedNum > 0)
+ return true;
+ if (Math.random() > 0.5)
+ return true;
+ }
+ return false;
+}
+
+function createTrunk(fromPosition, fromRadius, toPosition, segments, maxRadius)
+{
+ var component = document.createComponent();
+ var part = document.createPart(component);
+ if (SUBDIVED)
+ document.setAttribute(part, "subdived", "true");
+ document.setAttribute(part, "color", TRUNK_COLOR);
+ var previousNode = undefined;
+ var toRadius = fromRadius * 0.2;
+ var direction = toPosition.clone().sub(fromPosition).normalize();
+ var length = fromPosition.distanceTo(toPosition);
+ for (var i = 0; i < segments; ++i) {
+ var alpha = (i + 0.0) / segments;
+ var origin = fromPosition.clone().lerp(toPosition, alpha);
+ var radius = fromRadius * (1.0 - alpha) + toRadius * alpha;
+ radius += radius * MAX_RADIUS_OFFSET * Math.random();
+ if (undefined != maxRadius && radius > maxRadius)
+ radius = maxRadius;
+ var oldY = origin.y;
+ origin.random(length * 0.1 * MAX_ORIGIN_OFFSET);
+ origin.setY(oldY + radius * 0.5 * Math.random());
+ if (undefined != maxRadius && i == 0)
+ origin = fromPosition;
+ var node = createNode(part, origin, radius);
+ if (undefined != previousNode)
+ document.connect(previousNode, node);
+
+ var maxBranches = MAX_BRANCHES_PER_GROWTH * Math.random();
+ var alreadyGrowedBranchNum = 0;
+ var alreadyGrowedLeafNum = 0;
+ for (var j = 0; j < maxBranches; ++j) {
+ if (shouldGrowBranch(alpha, alreadyGrowedBranchNum)) {
+ var branchLength = length * randInRange(BRANCH_LENGTH_MIN_RATIO, BRANCH_LENGTH_MAX_RATIO);
+ var branchRadius = radius * 0.5;
+ var branchSegments = segments * (branchLength / length);
+ if (branchSegments >= 3 && branchRadius > TRUNK_RADIUS / 20) {
+ createBranch(origin, branchRadius, branchLength, branchSegments, direction, radius);
+ ++alreadyGrowedBranchNum;
+ }
+ }
+ if (undefined != maxRadius && shouldGrowLeaf(alreadyGrowedLeafNum)) {
+ createLeaf(origin, direction);
+ ++alreadyGrowedLeafNum;
+ }
+ }
+ previousNode = node;
+ }
+}
+
+var treeRootPosition = new THREE.Vector3(0.5, 1.0, 1.0);
+var treeTopPosition = treeRootPosition.clone();
+treeTopPosition.add((new THREE.Vector3(0, -1, 0)).multiplyScalar(TREE_HEIGHT));
+
+document.setAttribute(document.canvas, "originX", 0.506767);
+document.setAttribute(document.canvas, "originY", 0.615943);
+document.setAttribute(document.canvas, "originZ", 1.08543);
+
+createTrunk(treeRootPosition, TRUNK_RADIUS, treeTopPosition, TRUNK_SEGMENTS);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp
index 162cb608..4b9f2c0e 100644
--- a/src/documentwindow.cpp
+++ b/src/documentwindow.cpp
@@ -389,6 +389,7 @@ DocumentWindow::DocumentWindow() :
"Dog head",
"Meerkat",
"Mosquito",
+ "Procedural Tree",
};
for (const auto &model: exampleModels) {
QAction *openModelAction = new QAction(model, this);