<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Random Tree</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r132/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/r132/examples/js/controls/OrbitControls.js"></script>
<script>
var controls;
const NUM_POINTS = 1;
const LINE_MATERIAL = new THREE.LineBasicMaterial({ color: 0x550000 });
const LINE_LENGTH = 7.5;
const LINE_TAPER = 0.1;
const MAX_DEPTH = 10;
const ANGLE_RANGE = 0.5;
const BRANCHING_PROBABILITY = 0.5;
const SPHERE_RADIUS = 8.0;
const SPHERE_COLOR = 0x308030;
class Branch extends THREE.Object3D {
constructor(geometry, material, length, taper) {
super();
const line = new THREE.Line(geometry, material);
this.add(line);
this.length = length;
this.taper = taper;
}
}
class Tree {
constructor() {
this.branches = [];
this.maxDepth = MAX_DEPTH;
this.endPoints = [];
}
grow(depth, point, angle) {
if (depth >= this.maxDepth) {
this.endPoints.push(point);
return;
}
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(2 * 3);
positions.set([point.x, point.y, point.z, point.x, point.y, point.z], 0);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const direction = new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).normalize();
const axis = new THREE.Vector3();
axis.crossVectors(direction, point).normalize();
const angleChange = ANGLE_RANGE * Math.random() - ANGLE_RANGE / 2;
angle += angleChange;
const endPoint = point.clone().add(direction.clone().applyAxisAngle(axis, angle).multiplyScalar(LINE_LENGTH));
positions.set([point.x, point.y, point.z, endPoint.x, endPoint.y, endPoint.z], 0);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const newBranch = new Branch(geometry, LINE_MATERIAL, LINE_LENGTH, LINE_TAPER);
this.branches.push(newBranch);
if (Math.random() < BRANCHING_PROBABILITY) {
this.grow(depth + 1, endPoint.clone(), angle);
this.grow(depth + 1, endPoint.clone(), angle);
} else {
this.grow(depth + 1, endPoint.clone(), angle);
}
}
render(scene) {
for (let i = 0; i < this.branches.length; i++) {
scene.add(this.branches[i]);
}
for (let i = 0; i < this.endPoints.length; i++) {
const sphereGeometry = new THREE.SphereGeometry(SPHERE_RADIUS, 12, 12);
const sphereMaterial = new THREE.MeshPhongMaterial({ color: SPHERE_COLOR, shininess: 0.1 });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true;
sphere.receiveShadow = true;
sphere.position.copy(this.endPoints[i]);
scene.add(sphere);
}
}
}
function init() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
40, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 40;
camera.position.z = 120;
const sunLight = new THREE.PointLight(0xffffff, 3, 100, 1);
sunLight.position.set(10, 100, 30);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sunLight.shadow.camera.near = 1;
sunLight.shadow.camera.far = 1000;
sunLight.shadow.bias = -0.005;
scene.add(sunLight);
const ambientLight = new THREE.AmbientLight(0xeeeeff, 0.7);
scene.add(ambientLight);
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
const tree = new Tree();
const points = [];
for (let i = 0; i < NUM_POINTS; i++) {
points.push(
new THREE.Vector3(
0,
10,
0
)
);
}
for (let i = 0; i < points.length; i++) {
tree.grow(0, points[i], Math.PI / 2);
}
tree.render(scene);
function animate() {
requestAnimationFrame(animate);
scene.rotation.y += 0.002;
controls.update();
camera.lookAt(new THREE.Vector3(0, 50, 0));
renderer.render(scene, camera);
}
animate();
}
init();
</script>
</body>
</html>