{
const baseUrl =
"https://bumbeishvili.github.io/threejs-journey-17-daunted-house/";
function createBoxWithRoundedEdges(
width,
height,
depth,
radius0,
smoothness
) {
let shape = new THREE.Shape();
let eps = 0.00001;
let radius = radius0 - eps;
shape.absarc(eps, eps, eps, -Math.PI / 2, -Math.PI, true);
shape.absarc(eps, height - radius * 2, eps, Math.PI, Math.PI / 2, true);
shape.absarc(
width - radius * 2,
height - radius * 2,
eps,
Math.PI / 2,
0,
true
);
shape.absarc(width - radius * 2, eps, eps, 0, -Math.PI / 2, true);
let geometry = new THREE.ExtrudeBufferGeometry(shape, {
amount: depth - radius0 * 2,
bevelEnabled: true,
bevelSegments: smoothness * 2,
steps: 1,
bevelSize: radius,
bevelThickness: radius0,
curveSegments: smoothness
});
geometry.center();
return geometry;
}
function createPlaneWithRoundedEdges({
x = 0,
y = 0,
width = 50,
height = 50,
radius = 20
}) {
let shape = new THREE.Shape();
shape.moveTo(x, y + radius);
shape.lineTo(x, y + height - radius);
shape.quadraticCurveTo(x, y + height, x + radius, y + height);
shape.lineTo(x + width - radius, y + height);
shape.quadraticCurveTo(
x + width,
y + height,
x + width,
y + height - radius
);
shape.lineTo(x + width, y + radius);
shape.quadraticCurveTo(x + width, y, x + width - radius, y);
shape.lineTo(x + radius, y);
shape.quadraticCurveTo(x, y, x, y + radius);
let geometry = new THREE.ShapeBufferGeometry(shape);
return geometry;
}
const canvas = document.querySelector("canvas.webgl");
const scene = new THREE.Scene();
const textureLoader = new THREE.TextureLoader();
const house = new THREE.Group();
scene.add(house);
const houseWidth = 4;
const houseHeight = 3;
const wallsColorTexture = textureLoader.load(
baseUrl + "textures/bricks/color.jpg"
);
const wallsAmbientOcclusionTexture = textureLoader.load(
baseUrl + "textures/bricks/ambientOcclusion.jpg"
);
const wallsNormalTexture = textureLoader.load(
baseUrl + "textures/bricks/normal.jpg"
);
const wallsRoughnessTexture = textureLoader.load(
baseUrl + "textures/bricks/roughness.jpg"
);
const obj = {
wallsAmbientOcclusionTexture,
wallsColorTexture,
wallsNormalTexture,
wallsRoughnessTexture
};
Object.values(obj).forEach((texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(0.25, 0.25);
});
const walls = new THREE.Mesh(
createBoxWithRoundedEdges(houseWidth, houseHeight, houseWidth, 1 / 8, 16),
new THREE.MeshStandardMaterial({
color: "#ac8382",
aoMap: wallsAmbientOcclusionTexture,
normalMap: wallsNormalTexture,
roughnessMap: wallsRoughnessTexture,
map: wallsColorTexture
})
);
console.log(walls);
walls.geometry.setAttribute(
"uv2",
new THREE.Float32BufferAttribute(walls.geometry.attributes.uv.array, 2)
);
console.log(walls);
walls.position.y = houseHeight / 2;
house.add(walls);
const roofHeight = 2;
const roof = new THREE.Mesh(
new THREE.ConeBufferGeometry(houseWidth, roofHeight, 4),
new THREE.MeshStandardMaterial({ color: "#b35f45" })
);
roof.rotation.y = Math.PI / 4;
roof.position.y = houseHeight + roof.geometry.parameters.height / 2 - 0.2;
house.add(roof);
const doorColorTexture = textureLoader.load(
baseUrl + "textures/door/color.jpg"
);
const doorAlphaTexture = textureLoader.load(
baseUrl + "textures/door/alpha.jpg"
);
const doorAmbientOcclusionTexture = textureLoader.load(
baseUrl + "textures/door/ambientOcclusion.jpg"
);
const doorHeightTexture = textureLoader.load(
baseUrl + "textures/door/height.jpg"
);
const doorNormalTexture = textureLoader.load(
baseUrl + "textures/door/normal.jpg"
);
const doorMetalnessTexture = textureLoader.load(
baseUrl + "textures/door/metalness.jpg"
);
const doorRoughnessTexture = textureLoader.load(
baseUrl + "textures/door/roughness.jpg"
);
const doorWidth = 2;
const doorHeight = 2;
let panelGeometry = new THREE.PlaneBufferGeometry(2, 2, 100, 100);
panelGeometry.translate(1, 1, 0);
console.log(panelGeometry);
let door = new THREE.Mesh(
panelGeometry,
new THREE.MeshStandardMaterial({
map: doorColorTexture,
transparent: true,
alphaMap: doorAlphaTexture,
aoMap: doorAmbientOcclusionTexture,
displacementMap: doorHeightTexture,
displacementScale: 0.1,
normalMap: doorNormalTexture,
metalnessMap: doorMetalnessTexture,
roughnessMap: doorRoughnessTexture,
color: "#b35f45"
})
);
door.geometry.setAttribute(
"uv2",
new THREE.Float32BufferAttribute(door.geometry.attributes.uv.array, 2)
);
door.position.z = houseWidth / 2 + 0.001;
door.position.y = +0.05;
door.position.x = -doorWidth / 2;
house.add(door);
const grassColorTexture = textureLoader.load(
baseUrl + "textures/grass/color.jpg"
);
const grassAmbientOcclusionTexture = textureLoader.load(
baseUrl + "textures/grass/ambientOcclusion.jpg"
);
const grassNormalTexture = textureLoader.load(
baseUrl + "textures/grass/normal.jpg"
);
const grassRoughnessTexture = textureLoader.load(
baseUrl + "textures/grass/roughness.jpg"
);
const grasT = {
grassAmbientOcclusionTexture,
grassColorTexture,
grassNormalTexture,
grassRoughnessTexture
};
Object.values(grasT).forEach((texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(0.25, 0.25);
});
console.log(walls);
walls.position.y = houseHeight / 2;
const floor = new THREE.Mesh(
createBoxWithRoundedEdges(20, 0.4, 20, 1 / 16, 16),
new THREE.MeshStandardMaterial({
color: "#a9c388",
aoMap: grassAmbientOcclusionTexture,
normalMap: grassNormalTexture,
roughnessMap: grassRoughnessTexture,
map: grassColorTexture
})
);
floor.geometry.setAttribute(
"uv2",
new THREE.Float32BufferAttribute(floor.geometry.attributes.uv.array, 2)
);
floor.position.y = -0.2;
scene.add(floor);
const bushGeometry = new THREE.SphereBufferGeometry(1, 16, 16);
const bushMaterial = new THREE.MeshStandardMaterial({ color: "#89c854" });
const bushPositions = [
{ x: 0.8, y: 0.2, z: 2.2, scaleX: 0.5, scaleY: 0.5, scaleZ: 0.5 },
{ x: 1.4, y: 0.1, z: 2.1, scaleX: 0.25, scaleY: 0.25, scaleZ: 0.25 },
{ x: -0.8, y: 0.1, z: 2.2, scaleX: 0.4, scaleY: 0.4, scaleZ: 0.4 },
{ x: -1, y: 0.05, z: 2.6, scaleX: 0.15, scaleY: 0.15, scaleZ: 0.15 }
];
const bushes = bushPositions.map((bushPos) => {
const bush = new THREE.Mesh(bushGeometry, bushMaterial);
bush.scale.set(bushPos.scaleX, bushPos.scaleY, bushPos.scaleZ);
bush.position.set(bushPos.x, bushPos.y, bushPos.z);
scene.add(bush);
return bush;
});
const graves = new THREE.Group();
scene.add(graves);
const graveGeometry = createBoxWithRoundedEdges(0.6, 0.9, 0.2, 1 / 16, 4);
const graveMaterial = new THREE.MeshStandardMaterial({ color: "#b2b6b1" });
const gravesObjs = [];
for (let i = 0; i < 50; i++) {
const randomAngle = Math.random() * Math.PI * 2;
const radius = 3 + Math.random() * 6;
const z = Math.sin(randomAngle) * radius;
const x = Math.cos(randomAngle) * radius;
const grave = new THREE.Mesh(graveGeometry, graveMaterial);
grave.position.set(x, 0.3, z);
grave.rotation.z = Math.PI / (10 + Math.random() * 50);
gravesObjs.push(grave);
graves.add(grave);
}
const ambientLight = new THREE.AmbientLight("#b9d5ff", 0.12);
scene.add(ambientLight);
const moonLight = new THREE.DirectionalLight("#b9d5ff", 0.12);
moonLight.position.set(4, 5, -2);
scene.add(moonLight);
const doorLight = new THREE.PointLight("#ff7d46", 1, 5);
doorLight.position.set(0, 2.2, 2.7);
house.add(doorLight);
const ghostProps = [
{
color: "#ff00ff",
random: 2 + Math.random(),
intensity: 2,
distance: 3,
radius: 4.5,
speedScale: 1
},
{
color: "#00ffff",
random: 2 + Math.random(),
intensity: 2,
distance: 3,
radius: 5,
speedScale: 1.4
},
{
color: "#ffff00",
random: 2 + Math.random(),
intensity: 2,
distance: 3,
radius: 6,
speedScale: 1.8
}
];
const ghosts = ghostProps.map((props) => {
return new THREE.PointLight(props.color, props.intensity, props.distance);
});
scene.add(...ghosts);
const lights = [moonLight, doorLight, ...ghosts];
const shadowObjs = [...lights, walls, ...bushes, ...gravesObjs];
shadowObjs.forEach((light) => {
light.castShadow = true;
});
[floor].forEach((obj) => {
obj.receiveShadow = true;
});
moonLight.shadow.camera.near = 0.1;
moonLight.shadow.camera.far = 13;
doorLight.shadow.camera.far = 1;
lights.forEach((light) => {
light.shadow.mapSize.width = 256;
light.shadow.mapSize.height = 256;
});
const fog = new THREE.Fog("#262837", 1, 17);
scene.fog = fog;
const sizes = {
width: width,
height: 900
};
window.addEventListener("resize", () => {
sizes.width = width;
sizes.height = 900;
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
const camera = new THREE.PerspectiveCamera(
75,
sizes.width / sizes.height,
0.1,
100
);
camera.position.x = 4;
camera.position.y = 2;
camera.position.z = 15;
scene.add(camera);
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor("#262837");
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
const clock = new THREE.Clock();
const tick = () => {
const elapsedTime = clock.getElapsedTime();
ghosts.forEach((ghost, i) => {
const speed = ghostProps[i].speedScale * elapsedTime;
const rand = ghostProps[i].random;
const radius = ghostProps[i].radius;
const angle = speed * 0.5 * (i % 2 ? 1 : -1);
ghost.position.x =
Math.cos(angle) * (radius + Math.sin(elapsedTime) * 0.32);
ghost.position.z = Math.sin(angle) * radius;
ghost.position.y =
Math.abs((Math.sin(angle * 2) * radius) / 2) +
Math.sin(elapsedTime * rand);
});
controls.update();
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
};
tick();
controls.autoRotate = true;
}