Skip to content

Instantly share code, notes, and snippets.

@noone0
Created February 7, 2026 16:07
Show Gist options
  • Select an option

  • Save noone0/8a8fb9ac12ec92a34e6eda2141c9aae0 to your computer and use it in GitHub Desktop.

Select an option

Save noone0/8a8fb9ac12ec92a34e6eda2141c9aae0 to your computer and use it in GitHub Desktop.
Çenger Farm 3D Viewer
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Çenger Farm 3D</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{overflow:hidden;touch-action:none;font-family:system-ui,sans-serif}
canvas{display:block}
.ui{position:fixed;z-index:10}
.info{top:10px;left:10px;background:rgba(0,0,0,.8);color:#fff;padding:15px;border-radius:10px;font-size:13px;max-width:220px}
.info h3{color:#4fc3f7;margin-bottom:8px}
.info p{margin:4px 0;color:#ccc}
.btns{bottom:15px;left:50%;transform:translateX(-50%);display:flex;gap:8px}
.btns button{background:rgba(0,0,0,.8);color:#fff;border:1px solid #4fc3f7;padding:10px 15px;border-radius:6px;font-size:13px}
.tip{bottom:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.6);color:#aaa;padding:6px 12px;border-radius:15px;font-size:11px}
</style>
</head>
<body>
<div class="ui info">
<h3>🏡 Çenger Farm</h3>
<p>📍 Fethiye, Muğla</p>
<p>📐 5,361 m²</p>
<p style="margin-top:8px;font-size:11px;color:#888">🟢 Residential • 🟤 Barn<br>🟠 Field • 🌿 Garden</p>
</div>
<div class="ui tip">👆 Drag rotate • Pinch zoom</div>
<div class="ui btns">
<button onclick="goTo(0)">🎯 All</button>
<button onclick="goTo(1)">🏠 House</button>
<button onclick="goTo(2)">🏚️ Barn</button>
<button onclick="spin=!spin">🔄</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>
// Globals
let W=innerWidth, H=innerHeight, spin=false;
const scene=new THREE.Scene();
const cam=new THREE.PerspectiveCamera(60,W/H,1,1000);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(W,H);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
document.body.appendChild(renderer.domElement);
scene.background=new THREE.Color(0x7ec8e3);
cam.position.set(70,50,70);
cam.lookAt(0,0,0);
// Lights
scene.add(new THREE.AmbientLight(0xffffff,0.6));
const sun=new THREE.DirectionalLight(0xffffff,0.8);
sun.position.set(30,50,30);
scene.add(sun);
// Materials
const mat={
ground:new THREE.MeshLambertMaterial({color:0x8B7355}),
parcel:new THREE.MeshLambertMaterial({color:0x9B8B6B}),
field:new THREE.MeshLambertMaterial({color:0xCD853F}),
garden:new THREE.MeshLambertMaterial({color:0x66BB6A}),
house:new THREE.MeshLambertMaterial({color:0xFFF8DC}),
roof:new THREE.MeshLambertMaterial({color:0xB84C29}),
barn:new THREE.MeshLambertMaterial({color:0xA0522D}),
door:new THREE.MeshLambertMaterial({color:0x5D4037}),
tree:new THREE.MeshLambertMaterial({color:0x2E7D32}),
trunk:new THREE.MeshLambertMaterial({color:0x5D4037})
};
// Ground
const ground=new THREE.Mesh(new THREE.PlaneGeometry(200,200),mat.ground);
ground.rotation.x=-Math.PI/2;
ground.position.y=-0.5;
scene.add(ground);
// Parcel
const parcel=new THREE.Mesh(new THREE.PlaneGeometry(35,90),mat.parcel);
parcel.rotation.x=-Math.PI/2;
parcel.position.set(0,0,5);
scene.add(parcel);
// Cultivated Field
const field=new THREE.Mesh(new THREE.PlaneGeometry(28,22),mat.field);
field.rotation.x=-Math.PI/2;
field.position.set(0,0.1,38);
scene.add(field);
// Field rows
for(let i=-10;i<=10;i+=2.5){
const row=new THREE.Mesh(new THREE.BoxGeometry(0.4,0.2,20),new THREE.MeshLambertMaterial({color:0x8B4513}));
row.position.set(i,0.2,38);
scene.add(row);
}
// Garden
const garden=new THREE.Mesh(new THREE.PlaneGeometry(18,12),mat.garden);
garden.rotation.x=-Math.PI/2;
garden.position.set(0,0.1,15);
scene.add(garden);
// Garden beds
for(let x=-5;x<=5;x+=5){
for(let z=-3;z<=3;z+=3){
const bed=new THREE.Mesh(new THREE.BoxGeometry(3,0.3,2),new THREE.MeshLambertMaterial({color:0x4CAF50}));
bed.position.set(x,0.25,15+z);
scene.add(bed);
}
}
// Main House
const houseG=new THREE.Group();
const walls=new THREE.Mesh(new THREE.BoxGeometry(12,4,9),mat.house);
walls.position.y=2;
houseG.add(walls);
const roof=new THREE.Mesh(new THREE.ConeGeometry(8.5,3,4),mat.roof);
roof.position.y=5.5;
roof.rotation.y=Math.PI/4;
houseG.add(roof);
const door=new THREE.Mesh(new THREE.BoxGeometry(1.8,2.5,0.3),mat.door);
door.position.set(0,1.25,4.6);
houseG.add(door);
houseG.position.set(0,0,-8);
scene.add(houseG);
// Barn
const barnG=new THREE.Group();
const barnBase=new THREE.Mesh(new THREE.BoxGeometry(12,4.5,9),mat.barn);
barnBase.position.y=2.25;
barnG.add(barnBase);
const barnRoof=new THREE.Mesh(new THREE.ConeGeometry(8,2.5,4),mat.roof);
barnRoof.position.y=5.75;
barnRoof.rotation.y=Math.PI/4;
barnG.add(barnRoof);
const barnDoor=new THREE.Mesh(new THREE.BoxGeometry(3.5,3.5,0.3),mat.door);
barnDoor.position.set(0,1.75,4.6);
barnG.add(barnDoor);
barnG.position.set(-2,0,-30);
scene.add(barnG);
// Small shed
const shed=new THREE.Mesh(new THREE.BoxGeometry(6,2.5,5),mat.barn);
shed.position.set(4,1.25,26);
scene.add(shed);
const shedRoof=new THREE.Mesh(new THREE.BoxGeometry(7,0.3,6),mat.roof);
shedRoof.position.set(4,2.65,26);
scene.add(shedRoof);
// Trees
[[−22,−20],[−25,5],[−23,30],[−20,48],[22,−18],[25,8],[23,32],[20,50],[−18,−42],[18,−40]].forEach(p=>{
const t=new THREE.Group();
t.add(new THREE.Mesh(new THREE.CylinderGeometry(0.4,0.6,3,6),mat.trunk));
t.children[0].position.y=1.5;
const fol=new THREE.Mesh(new THREE.SphereGeometry(2.5,6,5),mat.tree);
fol.position.y=4.5;
t.add(fol);
t.position.set(p[0],0,p[1]);
scene.add(t);
});
// Parcel outline
const outline=new THREE.Line(
new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(-16,0.3,-42),
new THREE.Vector3(-14,0.3,50),
new THREE.Vector3(16,0.3,52),
new THREE.Vector3(18,0.3,-40),
new THREE.Vector3(-16,0.3,-42)
]),
new THREE.LineBasicMaterial({color:0xff0000})
);
scene.add(outline);
// Camera targets
const views=[
{p:[70,50,70],t:[0,0,5]}, // overview
{p:[22,12,8],t:[0,2,-8]}, // house
{p:[18,10,-18],t:[-2,2,-30]} // barn
];
function goTo(i){
const v=views[i];
const startP={x:cam.position.x,y:cam.position.y,z:cam.position.z};
const startT={x:target.x,y:target.y,z:target.z};
const st=Date.now();
(function anim(){
const t=Math.min((Date.now()-st)/1000,1);
const e=1-Math.pow(1-t,3);
cam.position.set(startP.x+(v.p[0]-startP.x)*e,startP.y+(v.p[1]-startP.y)*e,startP.z+(v.p[2]-startP.z)*e);
target.x=startT.x+(v.t[0]-startT.x)*e;
target.y=startT.y+(v.t[1]-startT.y)*e;
target.z=startT.z+(v.t[2]-startT.z)*e;
if(t<1)requestAnimationFrame(anim);
})();
}
window.goTo=goTo;
window.spin=false;
// Simple orbit controls
let drag=false,prevX=0,prevY=0,theta=Math.PI/4,phi=Math.PI/4,radius=100;
const target={x:0,y:0,z:5};
function updateCam(){
cam.position.x=target.x+radius*Math.sin(phi)*Math.cos(theta);
cam.position.y=target.y+radius*Math.cos(phi);
cam.position.z=target.z+radius*Math.sin(phi)*Math.sin(theta);
cam.lookAt(target.x,target.y,target.z);
}
renderer.domElement.addEventListener('pointerdown',e=>{drag=true;prevX=e.clientX;prevY=e.clientY});
renderer.domElement.addEventListener('pointerup',()=>drag=false);
renderer.domElement.addEventListener('pointerleave',()=>drag=false);
renderer.domElement.addEventListener('pointermove',e=>{
if(!drag)return;
theta+=(e.clientX-prevX)*0.01;
phi+=( e.clientY-prevY)*0.01;
phi=Math.max(0.2,Math.min(1.5,phi));
prevX=e.clientX;prevY=e.clientY;
updateCam();
});
renderer.domElement.addEventListener('wheel',e=>{
radius+=e.deltaY*0.1;
radius=Math.max(25,Math.min(150,radius));
updateCam();
},{passive:true});
// Touch zoom
let lastDist=0;
renderer.domElement.addEventListener('touchstart',e=>{
if(e.touches.length===2){
lastDist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);
}
},{passive:true});
renderer.domElement.addEventListener('touchmove',e=>{
if(e.touches.length===2){
const d=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);
radius+=( lastDist-d)*0.3;
radius=Math.max(25,Math.min(150,radius));
lastDist=d;
updateCam();
}
},{passive:true});
// Initial camera
theta=Math.PI/4;phi=Math.PI/4;radius=100;
updateCam();
// Render loop
(function render(){
requestAnimationFrame(render);
if(spin)theta+=0.005;
if(spin)updateCam();
renderer.render(scene,cam);
})();
// Resize
onresize=()=>{W=innerWidth;H=innerHeight;cam.aspect=W/H;cam.updateProjectionMatrix();renderer.setSize(W,H)};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment