<!DOCTYPE html>
<html lang="fa">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>House 3D</title>
<style>
html,body{
margin:0;
width:100%;
height:100%;
overflow:hidden;
background:#20262b;
}
body{
font-family:Arial,sans-serif;
}
canvas{
display:block;
width:100%;
height:100%;
}
#crosshair{
position:fixed;
left:50%;
top:50%;
width:4px;
height:4px;
transform:translate(-50%,-50%);
background:#fff;
border-radius:50%;
box-shadow:0 0 5px #000;
pointer-events:none;
}
#hud{
position:fixed;
left:16px;
bottom:15px;
color:rgba(255,255,255,.55);
font-size:12px;
text-shadow:0 2px 5px #000;
pointer-events:none;
}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="crosshair"></div>
<div id="hud">WASD • Shift • Space • V</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r160/three.min.js"></script>
<script>
/* =========================================================
SAFE START
هیچ صفحه شروعی وجود ندارد
========================================================= */
const canvas=document.getElementById("game");
let renderer;
let scene;
let camera;
try{
renderer=new THREE.WebGLRenderer({
canvas:canvas,
antialias:true,
powerPreference:"high-performance"
});
renderer.setSize(
window.innerWidth,
window.innerHeight,
false
);
renderer.setPixelRatio(
Math.min(window.devicePixelRatio,1.35)
);
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
renderer.outputColorSpace=THREE.SRGBColorSpace;
renderer.toneMapping=THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure=1.15;
scene=new THREE.Scene();
scene.background=new THREE.Color(
0x9bb4c5
);
scene.fog=new THREE.Fog(
0x9bb4c5,
45,
150
);
camera=new THREE.PerspectiveCamera(
72,
innerWidth/innerHeight,
.05,
250
);
}catch(e){
/* اگر WebGL اصلاً در مرورگر موجود نباشد */
canvas.style.background=
"linear-gradient(#7895aa 0%,#a8b9bd 52%,#38452f 53%,#293525 100%)";
}
/* =========================================================
MATERIALS
========================================================= */
if(renderer){
const M={
grass:new THREE.MeshStandardMaterial({
color:0x394a2d,
roughness:1
}),
grass2:new THREE.MeshStandardMaterial({
color:0x52663a,
roughness:1
}),
road:new THREE.MeshStandardMaterial({
color:0x202326,
roughness:.95
}),
asphalt2:new THREE.MeshStandardMaterial({
color:0x303438,
roughness:.9
}),
concrete:new THREE.MeshStandardMaterial({
color:0x777b79,
roughness:.92
}),
wall:new THREE.MeshStandardMaterial({
color:0xc8c0ae,
roughness:.82
}),
wallDark:new THREE.MeshStandardMaterial({
color:0x938c7d,
roughness:.9
}),
brick:new THREE.MeshStandardMaterial({
color:0x794c35,
roughness:.9
}),
roof:new THREE.MeshStandardMaterial({
color:0x303235,
roughness:.86
}),
wood:new THREE.MeshStandardMaterial({
color:0x62402a,
roughness:.75
}),
woodLight:new THREE.MeshStandardMaterial({
color:0x8b5d38,
roughness:.7
}),
black:new THREE.MeshStandardMaterial({
color:0x111416,
roughness:.35,
metalness:.65
}),
metal:new THREE.MeshStandardMaterial({
color:0x777d80,
roughness:.28,
metalness:.8
}),
glass:new THREE.MeshPhysicalMaterial({
color:0x8eb6c8,
roughness:.08,
metalness:.05,
transparent:true,
opacity:.42
}),
carpet:new THREE.MeshStandardMaterial({
color:0x45433d,
roughness:1
}),
screen:new THREE.MeshStandardMaterial({
color:0x142c42,
roughness:.2,
metalness:.15
}),
sofa:new THREE.MeshStandardMaterial({
color:0x454849,
roughness:.95
}),
white:new THREE.MeshStandardMaterial({
color:0xdad6cb,
roughness:.8
})
};
/* =========================================================
LIGHT
========================================================= */
const hemi=new THREE.HemisphereLight(
0xcfe4ff,
0x303725,
2.1
);
scene.add(hemi);
const sun=new THREE.DirectionalLight(
0xffe5bd,
3.8
);
sun.position.set(
-45,
60,
30
);
sun.castShadow=true;
sun.shadow.mapSize.set(
1536,
1536
);
sun.shadow.camera.left=-75;
sun.shadow.camera.right=75;
sun.shadow.camera.top=75;
sun.shadow.camera.bottom=-75;
sun.shadow.camera.near=.1;
sun.shadow.camera.far=180;
sun.shadow.bias=-.0004;
scene.add(sun);
/* =========================================================
HELPERS
========================================================= */
function box(
x,y,z,
sx,sy,sz,
material,
cast=true
){
const o=new THREE.Mesh(
new THREE.BoxGeometry(
sx,sy,sz
),
material
);
o.position.set(x,y,z);
o.castShadow=cast;
o.receiveShadow=true;
scene.add(o);
return o;
}
function cyl(
x,y,z,
r,h,
material,
segments=20
){
const o=new THREE.Mesh(
new THREE.CylinderGeometry(
r,
r*1.04,
h,
segments
),
material
);
o.position.set(x,y,z);
o.castShadow=true;
o.receiveShadow=true;
scene.add(o);
return o;
}
function sphere(
x,y,z,
r,
material
){
const o=new THREE.Mesh(
new THREE.SphereGeometry(
r,
20,
16
),
material
);
o.position.set(x,y,z);
o.castShadow=true;
o.receiveShadow=true;
scene.add(o);
return o;
}
/* =========================================================
GROUND
========================================================= */
box(
0,
-.3,
0,
180,
.6,
180,
M.grass
);
/* =========================================================
ROAD
========================================================= */
box(
0,
.015,
-28,
180,
.08,
13,
M.road,
false
);
box(
0,
.07,
-20.8,
180,
.14,
2.5,
M.concrete,
false
);
/* road lines */
for(let x=-85;x<=85;x+=9){
box(
x,
.075,
-28,
5,
.035,
.18,
M.white,
false
);
}
/* =========================================================
HOUSE FOUNDATION
========================================================= */
box(
0,
.35,
0,
28,
.7,
20,
M.concrete
);
/* wooden floor */
box(
0,
.76,
0,
25,
.18,
17,
M.wood
);
/* =========================================================
HOUSE WALLS
========================================================= */
/* left */
box(
-12.5,
4.5,
0,
1,
7.5,
18,
M.wall
);
/* right */
box(
12.5,
4.5,
0,
1,
7.5,
18,
M.wall
);
/* back */
box(
0,
4.5,
-8.5,
24,
7.5,
1,
M.wall
);
/* front wall pieces */
box(
-9,
4.5,
8.5,
7,
7.5,
1,
M.wall
);
box(
9,
4.5,
8.5,
7,
7.5,
1,
M.wall
);
box(
0,
7,
8.5,
11,
2.5,
1,
M.wall
);
/* =========================================================
ROOF
========================================================= */
const roof=new THREE.Mesh(
new THREE.ConeGeometry(
18,
6,
4
),
M.roof
);
roof.position.set(
0,
10.8,
0
);
roof.rotation.y=Math.PI/4;
roof.scale.z=.62;
roof.castShadow=true;
roof.receiveShadow=true;
scene.add(roof);
/* =========================================================
DOOR
========================================================= */
box(
0,
3.7,
8.0,
3.4,
5.9,
.18,
M.woodLight
);
/* door frame */
box(
-1.85,
3.7,
7.88,
.2,
6.3,
.3,
M.white
);
box(
1.85,
3.7,
7.88,
.2,
6.3,
.3,
M.white
);
box(
0,
6.8,
7.88,
4,
.2,
.3,
M.white
);
/* handle */
sphere(
.95,
3.6,
7.72,
.12,
M.metal
);
/* =========================================================
WINDOWS
========================================================= */
function makeWindow(x,y,z){
box(
x,y,z,
4.4,
3.1,
.12,
M.glass,
false
);
box(
x-2.25,
y,
z-.12,
.18,
3.5,
.25,
M.white
);
box(
x+2.25,
y,
z-.12,
.18,
3.5,
.25,
M.white
);
box(
x,
y+1.65,
z-.12,
4.6,
.18,
.25,
M.white
);
box(
x,
y-1.65,
z-.12,
4.6,
.18,
.25,
M.white
);
box(
x,
y,
z-.14,
.12,
3.1,
.25,
M.white
);
}
makeWindow(
-6,
4.5,
7.92
);
makeWindow(
6,
4.5,
7.92
);
/* =========================================================
COMPUTER DESK
========================================================= */
const dx=-5;
const dz=-2;
box(
dx,
2.15,
dz,
7,
.35,
2.3,
M.woodLight
);
/* desk legs */
for(const x of [
dx-2.9,
dx+2.9
]){
box(
x,
1.15,
dz-.8,
.28,
2.3,
.28,
M.darkWood||M.wood
);
box(
x,
1.15,
dz+.8,
.28,
2.3,
.28,
M.darkWood||M.wood
);
}
/* =========================================================
MONITOR
========================================================= */
box(
dx,
3.65,
dz,
4.1,
2.45,
.28,
M.black
);
box(
dx,
3.65,
dz-.18,
3.65,
2,
.035,
M.screen,
false
);
/* screen glow */
const monitorLight=new THREE.PointLight(
0x5599cc,
1.2,
5
);
monitorLight.position.set(
dx,
3.6,
dz+.1
);
scene.add(monitorLight);
/* stand */
box(
dx,
2.75,
dz,
.3,
1.3,
.3,
M.metal
);
box(
dx,
2.35,
dz,
1.4,
.12,
.7,
M.metal
);
/* =========================================================
KEYBOARD
========================================================= */
box(
dx,
2.38,
dz+1,
3,
.13,
.85,
M.black
);
for(let r=0;r<4;r++){
for(let c=0;c<13;c++){
box(
dx-1.32+c*.22,
2.46,
dz+.73+r*.19,
.14,
.035,
.11,
M.white,
false
);
}
}
/* mouse */
sphere(
dx+2,
2.45,
dz+1,
.24,
M.black
);
/* =========================================================
PC CASE
========================================================= */
box(
dx+4.1,
1.9,
dz,
1.6,
3.5,
2.3,
M.black
);
box(
dx+3.25,
1.9,
dz,
.025,
2.7,
1.7,
M.glass,
false
);
/* =========================================================
CHAIR
========================================================= */
box(
dx,
1.35,
dz+3,
2.3,
.3,
2.1,
M.sofa
);
box(
dx,
2.6,
dz+3.85,
2.3,
2.7,
.3,
M.sofa
);
cyl(
dx,
.7,
dz+3,
.14,
1.3,
M.metal
);
/* =========================================================
SOFA
========================================================= */
box(
5,
1.25,
-1,
7,
1,
2.4,
M.sofa
);
box(
5,
2.65,
-2,
7,
2.8,
.45,
M.sofa
);
box(
1.5,
2,
-1,
.55,
2,
2.5,
M.sofa
);
box(
8.5,
2,
-1,
.55,
2,
2.5,
M.sofa
);
/* cushions */
for(let x=3;x<=7;x+=2){
box(
x,
1.85,
-1,
1.5,
.3,
1.35,
M.woodLight
);
}
/* =========================================================
TABLE
========================================================= */
cyl(
5,
1.25,
3.1,
2,
.3,
M.woodLight,
32
);
cyl(
5,
.65,
3.1,
.18,
1.3,
M.metal
);
/* =========================================================
TV
========================================================= */
box(
5,
3.8,
-7.95,
6.5,
3.7,
.3,
M.black
);
box(
5,
3.8,
-8.14,
5.8,
3,
.04,
M.screen,
false
);
/* =========================================================
CEILING LIGHTS
========================================================= */
function ceilingLight(x,z){
const light=new THREE.PointLight(
0xffd7a2,
2.7,
12
);
light.position.set(
x,
6.8,
z
);
light.castShadow=true;
scene.add(light);
const fixture=new THREE.Mesh(
new THREE.SphereGeometry(
.3,
16,
12
),
new THREE.MeshBasicMaterial({
color:0xffe6ba
})
);
fixture.position.set(
x,
6.7,
z
);
scene.add(fixture);
}
ceilingLight(-6,3);
ceilingLight(6,3);
/* =========================================================
OUTDOOR TREES
========================================================= */
function tree(x,z,s){
s=s||1;
cyl(
x,
2*s,
z,
.38*s,
4*s,
M.wood,
14
);
const crown=new THREE.Mesh(
new THREE.IcosahedronGeometry(
2.6*s,
1
),
M.grass2
);
crown.position.set(
x,
5*s,
z
);
crown.castShadow=true;
crown.receiveShadow=true;
scene.add(crown);
}
tree(-20,-3,1.3);
tree(-28,8,1.6);
tree(20,1,1.4);
tree(28,10,1.7);
tree(-30,-17,1.2);
tree(30,-17,1.4);
tree(-15,-32,1.4);
tree(16,-32,1.7);
/* =========================================================
FENCE
========================================================= */
function fence(z){
for(let x=-38;x<=38;x+=4){
box(
x,
1.15,
z,
.16,
2.3,
.16,
M.wood
);
}
box(
0,
1.7,
z,
78,
.18,
.18,
M.wood
);
box(
0,
.75,
z,
78,
.18,
.18,
M.wood
);
}
fence(-38);
/* =========================================================
RANDOM ROCKS
========================================================= */
for(let i=0;i<45;i++){
const x=(Math.random()-.5)*75;
const z=(Math.random()-.5)*75;
if(
Math.abs(x)<16 &&
Math.abs(z)<14
) continue;
const r=.12+
Math.random()*.35;
const rock=new THREE.Mesh(
new THREE.DodecahedronGeometry(
r,
0
),
M.concrete
);
rock.position.set(
x,
r,
z
);
rock.rotation.set(
Math.random(),
Math.random(),
Math.random()
);
rock.castShadow=true;
scene.add(rock);
}
/* =========================================================
PLAYER
========================================================= */
const player={
pos:new THREE.Vector3(
0,
1.7,
18
),
yaw:Math.PI,
pitch:0,
vy:0,
grounded:true,
third:false
};
/* =========================================================
MOUSE
کاملاً بدون کلیک
بدون POINTER LOCK
========================================================= */
let mouseX=0;
let mouseY=0;
let hasMouse=false;
document.addEventListener(
"mousemove",
e=>{
if(!hasMouse){
mouseX=e.clientX;
mouseY=e.clientY;
hasMouse=true;
return;
}
const dx=
e.clientX-mouseX;
const dy=
e.clientY-mouseY;
mouseX=e.clientX;
mouseY=e.clientY;
player.yaw-=dx*.002;
player.pitch-=dy*.002;
player.pitch=Math.max(
-1.4,
Math.min(
1.4,
player.pitch
)
);
}
);
/* =========================================================
KEYBOARD
========================================================= */
const keys={};
document.addEventListener(
"keydown",
e=>{
keys[e.code]=true;
if(e.code==="Space"){
e.preventDefault();
if(player.grounded){
player.vy=6.2;
player.grounded=false;
}
}
if(e.code==="KeyV"){
player.third=
!player.third;
}
}
);
document.addEventListener(
"keyup",
e=>{
keys[e.code]=false;
}
);
/* =========================================================
MOVEMENT
========================================================= */
const clock=new THREE.Clock();
function update(dt){
let f=0;
let s=0;
if(keys.KeyW)f++;
if(keys.KeyS)f--;
if(keys.KeyD)s++;
if(keys.KeyA)s--;
const speed=
keys.ShiftLeft||
keys.ShiftRight
?8
:4.5;
if(f||s){
const forward=
new THREE.Vector3(
Math.sin(player.yaw),
0,
Math.cos(player.yaw)
);
const right=
new THREE.Vector3(
Math.cos(player.yaw),
0,
-Math.sin(player.yaw)
);
const move=
new THREE.Vector3();
move.addScaledVector(
forward,
f
);
move.addScaledVector(
right,
s
);
move.normalize();
player.pos.addScaledVector(
move,
speed*dt
);
}
/* gravity */
player.vy-=17*dt;
player.pos.y+=
player.vy*dt;
if(player.pos.y<=1.7){
player.pos.y=1.7;
player.vy=0;
player.grounded=true;
}
/* world */
player.pos.x=
THREE.MathUtils.clamp(
player.pos.x,
-70,
70
);
player.pos.z=
THREE.MathUtils.clamp(
player.pos.z,
-70,
70
);
/* =====================================================
CAMERA
===================================================== */
if(!player.third){
camera.position.copy(
player.pos
);
const look=new THREE.Vector3(
Math.sin(player.yaw)*
Math.cos(player.pitch),
Math.sin(player.pitch),
Math.cos(player.yaw)*
Math.cos(player.pitch)
);
camera.lookAt(
player.pos.x+look.x,
player.pos.y+look.y,
player.pos.z+look.z
);
}else{
const distance=7;
camera.position.set(
player.pos.x-
Math.sin(player.yaw)*distance,
player.pos.y+2.2,
player.pos.z-
Math.cos(player.yaw)*distance
);
camera.lookAt(
player.pos.x,
player.pos.y+1,
player.pos.z
);
}
}
/* =========================================================
RESIZE
=⚠️Content was pasted as plain text and auto-formatted as a code block. Use the Code Block button in the editor for proper formatting.