<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Christmas Tree</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.140.1/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.140.1/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.140.1/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.2/dist/gsap.min.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #1a1a2e;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: Arial, sans-serif;
z-index: 100;
}
</style>
</head>
<body>
<div id="info">
<p>Click and drag to rotate the scene</p>
<p>Scroll to zoom in/out</p>
</div>
<script>
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x1a1a2e);
document.body.appendChild(renderer.domElement);
// Controls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
camera.position.set(0, 5, 15);
// Enhanced snow ground with texture and depth
const groundGeometry = new THREE.PlaneGeometry(100, 100, 50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.95,
metalness: 0.05,
bumpScale: 0.1
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.5;
// Add subtle snow dunes and irregularities
const vertices = groundGeometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i];
const z = vertices[i + 2];
// Create gentle snow drifts
const noise = Math.sin(x * 0.1) * Math.sin(z * 0.1) * 0.1;
vertices[i + 1] = noise;
}
groundGeometry.attributes.position.needsUpdate = true;
// Add shadow properties
ground.receiveShadow = true;
scene.add(ground);
// Add snow piles and irregularities
for (let i = 0; i < 50; i++) {
const pileGeometry = new THREE.SphereGeometry(0.3 + Math.random() * 0.5, 16, 16);
const pileMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.9,
metalness: 0.1
});
const pile = new THREE.Mesh(pileGeometry, pileMaterial);
pile.position.x = (Math.random() - 0.5) * 40;
pile.position.z = (Math.random() - 0.5) * 40;
pile.position.y = -0.3 + Math.random() * 0.2;
pile.scale.set(1 + Math.random(), 0.5 + Math.random() * 0.5, 1 + Math.random());
pile.receiveShadow = true;
scene.add(pile);
}
// Christmas tree (Pixar style with improved realism)
function createChristmasTree() {
const treeGroup = new THREE.Group();
// Tree trunk with bark texture effect
const trunkGeometry = new THREE.CylinderGeometry(0.5, 0.7, 2, 32);
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.95,
metalness: 0.05,
bumpScale: 0.1
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = 1;
// Add subtle bark details
trunk.scale.set(1, 1, 1);
trunk.userData = { isTrunk: true };
treeGroup.add(trunk);
// Improved tree leaves with more natural shape and color variation
const leafColors = [0x006400, 0x228B22, 0x008000, 0x32CD32, 0x00FF00];
for (let i = 0; i < 8; i++) {
const radius = 2.5 - i * 0.28;
const height = 1.8 - i * 0.2;
const segments = 48;
// Create a more organic cone shape
const leafGeometry = new THREE.ConeGeometry(radius, height, segments);
const leafMaterial = new THREE.MeshStandardMaterial({
color: leafColors[i % leafColors.length],
roughness: 0.85,
metalness: 0.15,
shininess: 40,
bumpScale: 0.05
});
const leaf = new THREE.Mesh(leafGeometry, leafMaterial);
// Stagger the positions for more natural look
leaf.position.y = 2.8 + i * 0.7;
// Add slight curve and asymmetry for organic appearance
const curveFactor = Math.sin(i * 0.5) * 0.1;
leaf.scale.set(1 + curveFactor, 1.1, 1 - curveFactor);
// Slight rotation for each layer
leaf.rotation.y = i * 0.1;
leaf.userData = { isLeaf: true };
treeGroup.add(leaf);
}
// Tree topper (star)
const starGeometry = new THREE.IcosahedronGeometry(0.5, 0);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xFFD700,
emissive: 0xFFD700,
emissiveIntensity: 0.3,
roughness: 0.2,
metalness: 0.8
});
const star = new THREE.Mesh(starGeometry, starMaterial);
star.position.y = 7.5;
star.scale.set(1.2, 1.5, 1.2);
treeGroup.add(star);
// Decorations
const decorationColors = [0xFF0000, 0xFFA500, 0xFFC0CB, 0x800080, 0x00BFFF];
for (let i = 0; i < 30; i++) {
const decorationGeometry = new THREE.SphereGeometry(0.2, 16, 16);
const decorationMaterial = new THREE.MeshStandardMaterial({
color: decorationColors[i % decorationColors.length],
emissive: decorationColors[i % decorationColors.length],
emissiveIntensity: 0.2,
roughness: 0.1,
metalness: 0.9
});
const decoration = new THREE.Mesh(decorationGeometry, decorationMaterial);
// Random position on tree
const angle = Math.random() * Math.PI * 2;
const radius = Math.random() * 2 + 0.5;
const height = Math.random() * 5 + 2;
decoration.position.x = Math.cos(angle) * radius;
decoration.position.z = Math.sin(angle) * radius;
decoration.position.y = height;
// Check if decoration is inside the tree
const maxRadiusAtHeight = 2.5 - (height - 2) * 0.4;
if (Math.sqrt(decoration.position.x ** 2 + decoration.position.z ** 2) < maxRadiusAtHeight) {
treeGroup.add(decoration);
}
}
return treeGroup;
}
const christmasTree = createChristmasTree();
scene.add(christmasTree);
// Improved Santa Claus model with better proportions and details
function createSanta() {
const santaGroup = new THREE.Group();
// Body with better proportions
const bodyGeometry = new THREE.CylinderGeometry(0.7, 0.9, 1.5, 32);
const bodyMaterial = new THREE.MeshStandardMaterial({
color: 0xC41E3A, // Deeper red
roughness: 0.7,
metalness: 0.3,
bumpScale: 0.05
});
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 1.2;
santaGroup.add(body);
// Arms
const armGeometry = new THREE.CylinderGeometry(0.2, 0.15, 1.2, 16);
const armMaterial = new THREE.MeshStandardMaterial({
color: 0xC41E3A,
roughness: 0.7,
metalness: 0.3
});
const leftArm = new THREE.Mesh(armGeometry, armMaterial);
leftArm.position.set(-0.9, 1.3, 0);
leftArm.rotation.z = Math.PI * 0.3;
santaGroup.add(leftArm);
const rightArm = new THREE.Mesh(armGeometry, armMaterial);
rightArm.position.set(0.9, 1.3, 0);
rightArm.rotation.z = -Math.PI * 0.3;
santaGroup.add(rightArm);
// Hands
const handGeometry = new THREE.SphereGeometry(0.15, 16, 16);
const handMaterial = new THREE.MeshStandardMaterial({
color: 0xFFDAB9,
roughness: 0.9,
metalness: 0.1
});
const leftHand = new THREE.Mesh(handGeometry, handMaterial);
leftHand.position.set(-1.5, 1.5, 0);
santaGroup.add(leftHand);
const rightHand = new THREE.Mesh(handGeometry, handMaterial);
rightHand.position.set(1.5, 1.5, 0);
santaGroup.add(rightHand);
// Head with more realistic shape
const headGeometry = new THREE.SphereGeometry(0.45, 32, 32);
const headMaterial = new THREE.MeshStandardMaterial({
color: 0xFFDAB9,
roughness: 0.85,
metalness: 0.15
});
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 2.4;
santaGroup.add(head);
// Face details
const eyeGeometry = new THREE.SphereGeometry(0.05, 16, 16);
const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });
const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
leftEye.position.set(-0.15, 2.5, 0.4);
santaGroup.add(leftEye);
const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
rightEye.position.set(0.15, 2.5, 0.4);
santaGroup.add(rightEye);
// Nose
const noseGeometry = new THREE.SphereGeometry(0.08, 16, 16);
const noseMaterial = new THREE.MeshStandardMaterial({ color: 0xFF6347 });
const nose = new THREE.Mesh(noseGeometry, noseMaterial);
nose.position.set(0, 2.4, 0.42);
santaGroup.add(nose);
// Hat with fur trim
const hatBaseGeometry = new THREE.CylinderGeometry(0.45, 0.45, 0.2, 32);
const hatBaseMaterial = new THREE.MeshStandardMaterial({ color: 0xC41E3A });
const hatBase = new THREE.Mesh(hatBaseGeometry, hatBaseMaterial);
hatBase.position.y = 2.75;
santaGroup.add(hatBase);
const hatTopGeometry = new THREE.ConeGeometry(0.2, 0.8, 32);
const hatTop = new THREE.Mesh(hatTopGeometry, hatBaseMaterial);
hatTop.position.y = 3.15;
santaGroup.add(hatTop);
// Fur trim
const furGeometry = new THREE.TorusGeometry(0.5, 0.08, 16, 32);
const furMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFFFF });
const fur = new THREE.Mesh(furGeometry, furMaterial);
fur.position.y = 2.75;
fur.rotation.x = Math.PI / 2;
santaGroup.add(fur);
// Beard
const beardGeometry = new THREE.ConeGeometry(0.4, 0.6, 32);
const beardMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFFFF });
const beard = new THREE.Mesh(beardGeometry, beardMaterial);
beard.position.y = 2.2;
beard.rotation.x = Math.PI;
santaGroup.add(beard);
// Position Santa to the side
santaGroup.position.set(-5, 0, 3);
santaGroup.scale.set(1, 1, 1);
return santaGroup;
}
const santa = createSanta();
scene.add(santa);
// Improved snowflakes with better visuals and behavior
function createSnowflakes(count) {
const snowflakes = [];
// Create different snowflake geometries for variety
const snowflakeGeometries = [
new THREE.IcosahedronGeometry(0.05, 0),
new THREE.OctahedronGeometry(0.05, 0),
new THREE.TetrahedronGeometry(0.05, 0)
];
const snowflakeMaterial = new THREE.MeshStandardMaterial({
color: 0xFFFFFF,
transparent: true,
opacity: 0.9,
roughness: 0.1,
metalness: 0.9,
emissive: 0xFFFFFF,
emissiveIntensity: 0.1
});
for (let i = 0; i < count; i++) {
// Randomly choose a snowflake geometry
const geometry = snowflakeGeometries[Math.floor(Math.random() * snowflakeGeometries.length)];
const snowflake = new THREE.Mesh(geometry, snowflakeMaterial);
// Random position with more variation
snowflake.position.x = (Math.random() - 0.5) * 60;
snowflake.position.y = Math.random() * 25 + 5;
snowflake.position.z = (Math.random() - 0.5) * 60;
// Random size with more realistic range
const size = Math.random() * 0.06 + 0.01;
snowflake.scale.set(size, size, size);
// Add to scene and array with more properties for realistic movement
scene.add(snowflake);
snowflakes.push({
mesh: snowflake,
speed: Math.random() * 0.02 + 0.005,
rotationSpeed: Math.random() * 0.03 - 0.015,
swaySpeed: Math.random() * 0.02 + 0.01,
swayAmount: Math.random() * 0.3 + 0.1,
initialX: snowflake.position.x,
initialZ: snowflake.position.z,
timeOffset: Math.random() * Math.PI * 2
});
}
return snowflakes;
}
const snowflakes = createSnowflakes(200);
// Enhanced lighting for more realistic and festive atmosphere
function createLights() {
// Main directional light (sun) with warm winter tone
const dirLight = new THREE.DirectionalLight(0xFFFAF0, 0.8);
dirLight.position.set(8, 15, 10);
dirLight.castShadow = true;
// Configure shadow properties for better quality
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 0.5;
dirLight.shadow.camera.far = 50;
dirLight.shadow.camera.left = -20;
dirLight.shadow.camera.right = 20;
dirLight.shadow.camera.top = 20;
dirLight.shadow.camera.bottom = -20;
scene.add(dirLight);
// Ambient light with cool winter tone
const ambientLight = new THREE.AmbientLight(0xB0E0E6, 0.4);
scene.add(ambientLight);
// Rim light for depth
const rimLight = new THREE.DirectionalLight(0x87CEEB, 0.3);
rimLight.position.set(-5, 5, -5);
scene.add(rimLight);
// Colored lights for Christmas atmosphere with pulsing effect
const coloredLights = [];
const lightColors = [0xFF0000, 0x00FF00, 0xFFFF00, 0xFF69B4, 0x00BFFF];
for (let i = 0; i < 8; i++) {
const color = lightColors[i % lightColors.length];
const intensity = 0.6 + Math.random() * 0.4;
const range = 8 + Math.random() * 4;
const light = new THREE.PointLight(color, intensity, range);
// Random position around the tree
const angle = Math.random() * Math.PI * 2;
const radius = 2 + Math.random() * 2;
const height = 2 + Math.random() * 5;
light.position.x = Math.cos(angle) * radius;
light.position.y = height;
light.position.z = Math.sin(angle) * radius;
// Add light to scene and store for animation
scene.add(light);
coloredLights.push({
light: light,
baseIntensity: intensity,
pulseSpeed: 1 + Math.random() * 2,
pulseOffset: Math.random() * Math.PI * 2
});
}
// Store lights for animation
scene.userData.coloredLights = coloredLights;
}
// Animate colored lights
function animateLights() {
if (!scene.userData.coloredLights) return;
const time = Date.now() * 0.001;
scene.userData.coloredLights.forEach(lightData => {
const pulse = Math.sin(time * lightData.pulseSpeed + lightData.pulseOffset) * 0.3 + 0.7;
lightData.light.intensity = lightData.baseIntensity * pulse;
});
}
createLights();
// Enhanced snowflake animation with realistic movement
function animateSnowflakes() {
const time = Date.now() * 0.001;
snowflakes.forEach(snowflake => {
// Move down with slight acceleration
snowflake.mesh.position.y -= snowflake.speed * (1 + Math.sin(time * 0.5) * 0.2);
// Rotate with varying speed
snowflake.mesh.rotation.x += snowflake.rotationSpeed * (1 + Math.cos(time * 0.3) * 0.3);
snowflake.mesh.rotation.y += snowflake.rotationSpeed * (1 + Math.sin(time * 0.4) * 0.3);
// Swaying motion for more natural falling
const swayTime = time * snowflake.swaySpeed + snowflake.timeOffset;
snowflake.mesh.position.x = snowflake.initialX + Math.sin(swayTime) * snowflake.swayAmount;
snowflake.mesh.position.z = snowflake.initialZ + Math.cos(swayTime) * snowflake.swayAmount * 0.5;
// Gentle pulsing effect
const pulse = Math.sin(time * 2 + snowflake.timeOffset) * 0.1 + 0.9;
snowflake.mesh.material.opacity = 0.8 + pulse * 0.1;
// Reset if fallen below ground with new random properties
if (snowflake.mesh.position.y < -1) {
snowflake.mesh.position.y = 25;
snowflake.initialX = (Math.random() - 0.5) * 60;
snowflake.initialZ = (Math.random() - 0.5) * 60;
snowflake.mesh.position.x = snowflake.initialX;
snowflake.mesh.position.z = snowflake.initialZ;
snowflake.timeOffset = Math.random() * Math.PI * 2;
}
});
}
// Animate decorations
function animateDecorations() {
christmasTree.children.forEach(child => {
if (child.material && child.material.emissive) {
// Pulsing effect for decorations
const time = Date.now() * 0.001;
const pulse = Math.sin(time * 3) * 0.2 + 0.8;
child.material.emissiveIntensity = pulse * 0.3;
}
});
}
// Enhanced main animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
animateSnowflakes();
animateDecorations();
animateLights();
// More natural Santa animation
const time = Date.now() * 0.001;
santa.rotation.y = Math.sin(time * 0.5) * 0.3;
// Subtle breathing effect for Santa
const breathScale = Math.sin(time * 2) * 0.02 + 1;
santa.scale.set(breathScale, breathScale, breathScale);
// Gentle tree swaying in the wind
const treeSway = Math.sin(time * 0.3) * 0.03;
christmasTree.rotation.y = treeSway;
renderer.render(scene, camera);
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start animation
animate();
// Add some initial animation with GSAP
gsap.from(christmasTree.scale, {
x: 0.1,
y: 0.1,
z: 0.1,
duration: 1.5,
ease: "back.out(1.7)"
});
gsap.from(santa.position, {
x: -10,
duration: 2,
delay: 0.5,
ease: "power2.out"
});
gsap.from(snowflakes, {
opacity: 0,
duration: 2,
stagger: 0.01
});
</script>
</body>
</html>