• About Us
  • Privacy Policy
  • Disclaimer
  • Contact Us
AimactGrow
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing
No Result
View All Result
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing
No Result
View All Result
AimactGrow
No Result
View All Result

Exploring the Means of Constructing a Procedural 3D Kitchen Designer with Three.js

Admin by Admin
July 29, 2025
Home Coding
Share on FacebookShare on Twitter


Again in November 2024, I shared a submit on X a few software I used to be constructing to assist visualize kitchen remodels. The response from the Three.js group was overwhelmingly optimistic. The demo confirmed how procedural rendering methods—typically utilized in video games—might be utilized to real-world use instances like designing and rendering a complete kitchen in underneath 60 seconds.

On this article, I’ll stroll via the method and considering behind constructing this type of procedural 3D kitchen design software utilizing vanilla Three.js and TypeScript—from drawing partitions and defining cupboard segments to auto-generating full kitchen layouts. Alongside the best way, I’ll share key technical decisions, classes realized, and concepts for the place this might evolve subsequent.

Have been wanting to revamp my mother and father’ kitchen for some time now

…so I constructed them a bit 3D kitchen design-tool with @threejs, to allow them to shortly prototype floorplans/concepts

This is me designing a full kitchen rework in ~60s 🙂

I might love suggestions, or if anybody desires to strive pic.twitter.com/oX2SoHSfuj

— Nikolai Stakheiko (@thisisnikolai) November 5, 2024

You possibly can check out an interactive demo of the most recent model right here: https://kitchen-designer-demo.vercel.app/. (Tip: Press the “/” key to toggle between 2D and 3D views.)

Designing Room Layouts with Partitions

Instance of consumer drawing a easy room form utilizing the built-in wall module.

To provoke our mission, we start with the wall drawing module. At a excessive degree, that is akin to Figma’s pen software, the place the consumer can add one line section at a time till a closed—or open-ended—polygon is full on an infinite 2D canvas. In our construct, every line section represents a single wall as a 2D airplane from coordinate A to coordinate B, whereas the entire polygon outlines the perimeter envelope of a room.

  1. We start by capturing the [X, Z] coordinates (with Y oriented upwards) of the consumer’s preliminary click on on the infinite flooring airplane. This 2D level is obtained through Three.js’s built-in raycaster for intersection detection, establishing Level A.
  2. Because the consumer hovers the cursor over a brand new spot on the ground, we apply the identical intersection logic to find out a short lived Level B. Throughout this motion, a preview line section seems, connecting the mounted Level A to the dynamic Level B for visible suggestions.
  3. Upon the consumer’s second click on to substantiate Level B, we append the road section (outlined by Factors A and B) to an array of segments. The previous Level B immediately turns into the brand new Level A, permitting us to proceed the drawing course of with extra line segments.

Here’s a simplified code snippet demonstrating a fundamental 2D pen-draw software utilizing Three.js:

import * as THREE from 'three';

const scene = new THREE.Scene();
const digital camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
digital camera.place.set(0, 5, 10); // Place digital camera above the ground trying down
digital camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
doc.physique.appendChild(renderer.domElement);

// Create an infinite flooring airplane for raycasting
const floorGeometry = new THREE.PlaneGeometry(100, 100);
const floorMaterial = new THREE.MeshBasicMaterial({ coloration: 0xcccccc, facet: THREE.DoubleSide });
const flooring = new THREE.Mesh(floorGeometry, floorMaterial);
flooring.rotation.x = -Math.PI / 2; // Lay flat on XZ airplane
scene.add(flooring);

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let factors: THREE.Vector3[] = []; // i.e. wall endpoints
let tempLine: THREE.Line | null = null;
const partitions: THREE.Line[] = [];

operate getFloorIntersection(occasion: MouseEvent): THREE.Vector3 | null {
  mouse.x = (occasion.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(occasion.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, digital camera);
  const intersects = raycaster.intersectObject(flooring);
  if (intersects.size > 0) {
    // Spherical to simplify coordinates (non-obligatory for cleaner drawing)
    const level = intersects[0].level;
    level.x = Math.spherical(level.x);
    level.z = Math.spherical(level.z);
    level.y = 0; // Guarantee on flooring airplane
    return level;
  }
  return null;
}

// Replace non permanent line preview
operate onMouseMove(occasion: MouseEvent) {
  const level = getFloorIntersection(occasion);
  if (level && factors.size > 0) {
    // Take away previous temp line if exists
    if (tempLine) {
      scene.take away(tempLine);
      tempLine = null;
    }
    // Create new temp line from final level to present hover
    const geometry = new THREE.BufferGeometry().setFromPoints([points[points.length - 1], level]);
    const materials = new THREE.LineBasicMaterial({ coloration: 0x0000ff }); // Blue for temp
    tempLine = new THREE.Line(geometry, materials);
    scene.add(tempLine);
  }
}

// Add a brand new level and draw everlasting wall section
operate onMouseDown(occasion: MouseEvent) {
  if (occasion.button !== 0) return; // Left click on solely
  const level = getFloorIntersection(occasion);
  if (level) {
    factors.push(level);
    if (factors.size > 1) {
      // Draw everlasting wall line from earlier to present level
      const geometry = new THREE.BufferGeometry().setFromPoints([points[points.length - 2], factors[points.length - 1]]);
      const materials = new THREE.LineBasicMaterial({ coloration: 0xff0000 }); // Purple for everlasting
      const wall = new THREE.Line(geometry, materials);
      scene.add(wall);
      partitions.push(wall);
    }
    // Take away temp line after click on
    if (tempLine) {
      scene.take away(tempLine);
      tempLine = null;
    }
  }
}

// Add occasion listeners
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mousedown', onMouseDown);

// Animation loop
operate animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, digital camera);
}
animate();

The above code snippet is a really fundamental 2D pen software, and but this data is sufficient to generate a complete room occasion. For reference: not solely does every line section characterize a wall (2D airplane), however the set of amassed factors can be used to auto-generate the room’s flooring mesh, and likewise the ceiling mesh (the inverse of the ground mesh).

With a purpose to view the planes representing the partitions in 3D, one can rework every THREE.Line right into a customized Wall class object, which comprises each a line (for orthogonal 2D “flooring plan” view) and a 2D inward-facing airplane (for perspective 3D “room” view). To construct this class:

class Wall extends THREE.Group {
  constructor(size: quantity, peak: quantity = 96, thickness: quantity = 4) {
    tremendous();

    // 2D line for high view, alongside the x-axis
    const lineGeometry = new THREE.BufferGeometry().setFromPoints([
      new THREE.Vector3(0, 0, 0),
      new THREE.Vector3(length, 0, 0),
    ]);
    const lineMaterial = new THREE.LineBasicMaterial({ coloration: 0xff0000 });
    const line = new THREE.Line(lineGeometry, lineMaterial);
    this.add(line);

    // 3D wall as a field for thickness
    const wallGeometry = new THREE.BoxGeometry(size, peak, thickness);
    const wallMaterial = new THREE.MeshBasicMaterial({ coloration: 0xaaaaaa, facet: THREE.DoubleSide });
    const wall = new THREE.Mesh(wallGeometry, wallMaterial);
    wall.place.set(size / 2, peak / 2, 0);
    this.add(wall);
  }
}

We will now replace the wall draw module to make the most of this newly created Wall object:

// Replace our variables
let tempWall: Wall | null = null;
const partitions: Wall[] = [];

// Substitute line creation in onMouseDown with
if (factors.size > 1) {
  const begin = factors[points.length - 2];
  const finish = factors[points.length - 1];
  const course = finish.clone().sub(begin);
  const size = course.size();
  const wall = new Wall(size);
  wall.place.copy(begin);
  wall.rotation.y = Math.atan2(course.z, course.x); // Align alongside course (assuming CCW for inward dealing with)
  scene.add(wall);
  partitions.push(wall);
}

Upon including the ground and ceiling meshes, we are able to additional rework our wall module right into a room era module. To recap what we’ve simply created: by including partitions one after the other, we’ve given the consumer the flexibility to create full rooms with partitions, flooring, and ceilings—all of which might be adjusted later within the scene.

Person dragging out the wall in 3D perspective camera-view.

Producing Cupboards with Procedural Modeling

Our cabinet-related logic can include counter tops, base cupboards, and wall cupboards.

Fairly than taking a number of minutes so as to add the cupboards on a case-by-case foundation—for instance, like with IKEA’s 3D kitchen builder—it’s attainable so as to add all the cupboards without delay through a single consumer motion. One methodology to make use of right here is to permit the consumer to attract high-level cupboard line segments, in the identical method because the wall draw module.

On this module, every cupboard section will rework right into a linear row of base and wall cupboards, together with a parametrically generated countertop mesh on high of the bottom cupboards. Because the consumer creates the segments, we are able to mechanically populate this line section with pre-made 3D cupboard meshes in meshing software program like Blender. In the end, every cupboard’s width, depth, and peak parameters might be mounted, whereas the width of the final cupboard might be dynamic to fill the remaining area. We use a cupboard filler piece mesh right here—a daily plank, with its scale-X parameter stretched or compressed as wanted.

Creating the Cupboard Line Segments

Person could make a half-peninsula form by dragging the cabinetry line segments alongside the partitions, then in free-space.

Right here we’ll assemble a devoted cupboard module, with the aforementioned cupboard line section logic. This course of is similar to the wall drawing mechanism, the place customers can draw straight traces on the ground airplane utilizing mouse clicks to outline each begin and finish factors. Not like partitions, which might be represented by easy skinny traces, cupboard line segments must account for the standard depth of 24 inches to characterize the bottom cupboards’ footprint. These segments don’t require closing-polygon logic, as they are often standalone rows or L-shapes, as is frequent in most kitchen layouts.

We will additional enhance the consumer expertise by incorporating snapping performance, the place the endpoints of a cupboard line section mechanically align to close by wall endpoints or wall intersections, if inside a sure threshold (e.g., 4 inches). This ensures cupboards match snugly in opposition to partitions with out requiring handbook precision. For simplicity, we’ll define the snapping logic in code however give attention to the core drawing performance.

We will begin by defining the CabinetSegment class. Just like the partitions, this needs to be its personal class, as we’ll later add the auto-populating 3D cupboard fashions.

class CabinetSegment extends THREE.Group {
  public size: quantity;

  constructor(size: quantity, peak: quantity = 96, depth: quantity = 24, coloration: quantity = 0xff0000) {
    tremendous();
    this.size = size;
    const geometry = new THREE.BoxGeometry(size, peak, depth);
    const materials = new THREE.MeshBasicMaterial({ coloration, wireframe: true });
    const field = new THREE.Mesh(geometry, materials);
    field.place.set(size / 2, peak / 2, depth / 2); // Shift so depth spans 0 to depth (inward)
    this.add(field);
  }
}

As soon as we’ve the cupboard section, we are able to use it in a fashion similar to the wall line segments:

let cabinetPoints: THREE.Vector3[] = [];
let tempCabinet: CabinetSegment | null = null;
const cabinetSegments: CabinetSegment[] = [];
const CABINET_DEPTH = 24; // all the things in inches
const CABINET_SEGMENT_HEIGHT = 96; // i.e. each wall & base cupboards -> group ought to prolong to ceiling
const SNAPPING_DISTANCE = 4;

operate getSnappedPoint(level: THREE.Vector3): THREE.Vector3 {
  // Easy snapping: verify in opposition to current wall factors (wallPoints array from wall module)
  for (const wallPoint of wallPoints) {
    if (level.distanceTo(wallPoint) < SNAPPING_DISTANCE) return wallPoint;
  }
  return level;
}

// Replace non permanent cupboard preview
operate onMouseMoveCabinet(occasion: MouseEvent) {
  const level = getFloorIntersection(occasion);
  if (level && cabinetPoints.size > 0) {
    const snappedPoint = getSnappedPoint(level);
    if (tempCabinet) {
      scene.take away(tempCabinet);
      tempCabinet = null;
    }
    const begin = cabinetPoints[cabinetPoints.length - 1];
    const course = snappedPoint.clone().sub(begin);
    const size = course.size();
    if (size > 0) {
      tempCabinet = new CabinetSegment(size, CABINET_SEGMENT_HEIGHT, CABINET_DEPTH, 0x0000ff); // Blue for temp
      tempCabinet.place.copy(begin);
      tempCabinet.rotation.y = Math.atan2(course.z, course.x);
      scene.add(tempCabinet);
    }
  }
}

// Add a brand new level and draw everlasting cupboard section
operate onMouseDownCabinet(occasion: MouseEvent) {
  if (occasion.button !== 0) return;
  const level = getFloorIntersection(occasion);
  if (level) {
    const snappedPoint = getSnappedPoint(level);
    cabinetPoints.push(snappedPoint);
    if (cabinetPoints.size > 1) {
      const begin = cabinetPoints[cabinetPoints.length - 2];
      const finish = cabinetPoints[cabinetPoints.length - 1];
      const course = finish.clone().sub(begin);
      const size = course.size();
      if (size > 0) {
        const section = new CabinetSegment(size, CABINET_SEGMENT_HEIGHT, CABINET_DEPTH, 0xff0000); // Purple for everlasting
        section.place.copy(begin);
        section.rotation.y = Math.atan2(course.z, course.x);
        scene.add(section);
        cabinetSegments.push(section);
      }
    }
    if (tempCabinet) {
      scene.take away(tempCabinet);
      tempCabinet = null;
    }
  }
}

// Add separate occasion listeners for cupboard mode (e.g., toggled through UI button)
window.addEventListener('mousemove', onMouseMoveCabinet);
window.addEventListener('mousedown', onMouseDownCabinet);

Auto-Populating the Line Segments with Stay Cupboard Fashions

Right here we fill 2 line-segments with 3D cupboard fashions (base & wall), and countertop meshes.

As soon as the cupboard line segments are outlined, we are able to procedurally populate them with detailed elements. This entails dividing every section vertically into three layers: base cupboards on the backside, counter tops within the center, and wall cupboards above. For the bottom and wall cupboards, we’ll use an optimization operate to divide the section’s size into normal widths (preferring 30-inch cupboards), with any the rest stuffed utilizing the filler piece talked about above. Counter tops are even easier—they kind a single steady slab stretching the complete size of the section.

The bottom cupboards are set to 24 inches deep and 34.5 inches excessive. Counter tops add 1.5 inches in peak and prolong to 25.5 inches deep (together with a 1.5-inch overhang). Wall cupboards begin at 54 inches excessive (18 inches above the countertop), measure 12 inches deep, and are 30 inches tall. After producing these placeholder bounding bins, we are able to change them with preloaded 3D fashions from Blender utilizing a loading operate (e.g., through GLTFLoader).

// Constants in inches
const BASE_HEIGHT = 34.5;
const COUNTER_HEIGHT = 1.5;
const WALL_HEIGHT = 30;
const WALL_START_Y = 54;
const BASE_DEPTH = 24;
const COUNTER_DEPTH = 25.5;
const WALL_DEPTH = 12;

const DEFAULT_MODEL_WIDTH = 30;

// Filler-piece data
const FILLER_PIECE_FALLBACK_PATH = 'fashions/filler_piece.glb'
const FILLER_PIECE_WIDTH = 3;
const FILLER_PIECE_HEIGHT = 12;
const FILLER_PIECE_DEPTH = 24;

To deal with particular person cupboards, we’ll create a easy Cupboard class that manages the placeholder and mannequin loading.

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const loader = new GLTFLoader();

class Cupboard extends THREE.Group {
  constructor(width: quantity, peak: quantity, depth: quantity, modelPath: string, coloration: quantity) {
    tremendous();

    // Placeholder field
    const geometry = new THREE.BoxGeometry(width, peak, depth);
    const materials = new THREE.MeshBasicMaterial({ coloration });
    const placeholder = new THREE.Mesh(geometry, materials);
    this.add(placeholder);


    // Load and change with mannequin async

    // Case: Non-standard width -> use filler piece
    if (width < DEFAULT_MODEL_WIDTH) {
      loader.load(FILLER_PIECE_FALLBACK_PATH, (gltf) => {
        const mannequin = gltf.scene;
        mannequin.scale.set(
          width / FILLER_PIECE_WIDTH,
          peak / FILLER_PIECE_HEIGHT,
          depth / FILLER_PIECE_DEPTH,
        );
        this.add(mannequin);
        this.take away(placeholder);
      });
    }

    loader.load(modelPath, (gltf) => {
      const mannequin = gltf.scene;
      mannequin.scale.set(width / DEFAULT_MODEL_WIDTH, 1, 1); // Scale width
      this.add(mannequin);
      this.take away(placeholder);
    });
  }
}

Then, we are able to add a populate methodology to the present CabinetSegment class:

operate splitIntoCabinets(width: quantity): quantity[] {
  const cupboards = [];
  // Most well-liked width
  whereas (width >= DEFAULT_MODEL_WIDTH) {
    cupboards.push(DEFAULT_MODEL_WIDTH);
    width -= DEFAULT_MODEL_WIDTH;
  }
  if (width > 0) {
    cupboards.push(width); // Customized empty slot
  }
  return cupboards;
}

class CabinetSegment extends THREE.Group {
  // ... (current constructor and properties)

  populate() {
    // Take away placeholder line and field
    whereas (this.youngsters.size > 0) {
      this.take away(this.youngsters[0]);
    }

    let offset = 0;
    const widths = splitIntoCabinets(this.size);

    // Base cupboards
    widths.forEach((width) => {
      const baseCab = new Cupboard(width, BASE_HEIGHT, BASE_DEPTH, 'fashions/base_cabinet.glb', 0x8b4513);
      baseCab.place.set(offset + width / 2, BASE_HEIGHT / 2, BASE_DEPTH / 2);
      this.add(baseCab);
      offset += width;
    });

    // Countertop (single slab, no mannequin)
    const counterGeometry = new THREE.BoxGeometry(this.size, COUNTER_HEIGHT, COUNTER_DEPTH);
    const counterMaterial = new THREE.MeshBasicMaterial({ coloration: 0xa9a9a9 });
    const counter = new THREE.Mesh(counterGeometry, counterMaterial);
    counter.place.set(this.size / 2, BASE_HEIGHT + COUNTER_HEIGHT / 2, COUNTER_DEPTH / 2);
    this.add(counter);

    // Wall cupboards
    offset = 0;
    widths.forEach((width) => {
      const wallCab = new Cupboard(width, WALL_HEIGHT, WALL_DEPTH, 'fashions/wall_cabinet.glb', 0x4b0082);
      wallCab.place.set(offset + width / 2, WALL_START_Y + WALL_HEIGHT / 2, WALL_DEPTH / 2);
      this.add(wallCab);
      offset += width;
    });
  }
}

// Name for every cabinetSegment after drawing
cabinetSegments.forEach((section) => section.populate());

Additional Enhancements & Optimizations

We will additional enhance the scene with home equipment, varying-height cupboards, crown molding, and so forth.

At this level, we should always have the foundational parts of room and cupboard creation logic absolutely in place. With a purpose to take this mission from a rudimentary segment-drawing app into the sensible realm—together with dynamic cupboards, a number of sensible materials choices, and ranging actual equipment meshes—we are able to additional improve the consumer expertise via a number of focused refinements:

  • We will implement a detection mechanism to find out if a cupboard line section is in touch with a wall line section.
    • For cupboard rows that run parallel to partitions, we are able to mechanically incorporate a backsplash within the area between the wall cupboards and the countertop floor.
    • For cupboard segments not adjoining to partitions, we are able to take away the higher wall cupboards and prolong the countertop by an extra 15 inches, aligning with normal practices for kitchen islands or peninsulas.
  • We will introduce drag-and-drop performance for home equipment, every with predefined widths, permitting customers to place them alongside the road section. This integration will instruct our cabinet-splitting algorithm to exclude these areas from dynamic cupboard era.
  • Moreover, we can provide customers extra flexibility by enabling the swapping of 1 equipment with one other, making use of completely different textures to our 3D fashions, and adjusting default dimensions—equivalent to wall cupboard depth or countertop overhang—to swimsuit particular preferences.

All these core elements lead us to a complete, interactive utility that allows the fast rendering of an entire kitchen: cupboards, counter tops, and home equipment, in a totally interactive, user-driven expertise.

The intention of this mission is to display that complicated 3D duties might be distilled all the way down to easy consumer actions. It’s absolutely attainable to take the high-dimensional complexity of 3D tooling—with seemingly limitless controls—and encode these complexities into low-dimensional, simply adjustable parameters. Whether or not the developer chooses to show these parameters to the consumer or an LLM, the top result’s that traditionally difficult 3D processes can change into easy, and thus the whole contents of a 3D scene might be absolutely reworked with just a few parameters.

For those who discover any such growth attention-grabbing, have any nice concepts, or would like to contribute to the evolution of this product, I strongly welcome you to succeed in out to me through e mail. I firmly consider that solely lately has it change into attainable to construct residence design software program that’s so wickedly quick and intuitive that any individual—no matter architectural advantage—will be capable of design their very own single-family residence in lower than 5 minutes through an internet app, whereas absolutely adhering to native zoning, architectural, and design necessities. All of the infrastructure needed to perform this already exists; all it takes is a workforce of loopy, formidable builders seeking to change the usual of architectural residence design.



Tags: BuildingDesignerExploringKitchenProceduralProcessThree.js
Admin

Admin

Next Post
Shopflo Secures $20M in Funding Spherical Led by Binny Bansal, Units Its Sights on World Retail Tech Disruption

Shopflo Secures $20M in Funding Spherical Led by Binny Bansal, Units Its Sights on World Retail Tech Disruption

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recommended.

GSAP is Now Fully Free, Even for Business Use!

GSAP is Now Fully Free, Even for Business Use!

May 10, 2025
Police Tout Darknet International Takedown ‘Operation RapTor’

Police Tout Darknet International Takedown ‘Operation RapTor’

May 24, 2025

Trending.

How you can open the Antechamber and all lever places in Blue Prince

How you can open the Antechamber and all lever places in Blue Prince

April 14, 2025
ManageEngine Trade Reporter Plus Vulnerability Allows Distant Code Execution

ManageEngine Trade Reporter Plus Vulnerability Allows Distant Code Execution

June 10, 2025
Expedition 33 Guides, Codex, and Construct Planner

Expedition 33 Guides, Codex, and Construct Planner

April 26, 2025
Important SAP Exploit, AI-Powered Phishing, Main Breaches, New CVEs & Extra

Important SAP Exploit, AI-Powered Phishing, Main Breaches, New CVEs & Extra

April 28, 2025
Ubiquiti UniFi Shield Digital camera Vulnerability Permits Distant Code Execution by Attackers

Ubiquiti UniFi Shield Digital camera Vulnerability Permits Distant Code Execution by Attackers

May 8, 2025

AimactGrow

Welcome to AimactGrow, your ultimate source for all things technology! Our mission is to provide insightful, up-to-date content on the latest advancements in technology, coding, gaming, digital marketing, SEO, cybersecurity, and artificial intelligence (AI).

Categories

  • AI
  • Coding
  • Cybersecurity
  • Digital marketing
  • Gaming
  • SEO
  • Technology

Recent News

CL-STA-0969 Installs Covert Malware in Telecom Networks Throughout 10-Month Espionage Marketing campaign

CL-STA-0969 Installs Covert Malware in Telecom Networks Throughout 10-Month Espionage Marketing campaign

August 2, 2025
Why Website Well being Is Important For AI Search Visibility

Why Website Well being Is Important For AI Search Visibility

August 2, 2025
  • About Us
  • Privacy Policy
  • Disclaimer
  • Contact Us

© 2025 https://blog.aimactgrow.com/ - All Rights Reserved

No Result
View All Result
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing

© 2025 https://blog.aimactgrow.com/ - All Rights Reserved