Idea for CSS-like Styling for Three.js Geometries and Materials

Concept:

The idea is to bridge the familiar world of CSS with the dynamic realm of 3D graphics in Three.js. The goal is to create a library that allows developers to style Three.js geometries and materials using a subset of CSS syntax. This approach aims to make 3D styling more accessible, especially for those with a background in web development.

How It Would Work:

  • CSS Parsing: Utilizing a CSS parser (such as the one from PEG.js), the library will interpret CSS stylesheets or style strings.
  • Mapping CSS to Three.js: The parsed CSS properties will be mapped to corresponding properties in Three.js materials and geometries.
  • Dynamic Style Application: The library will provide functionality to apply these styles to Three.js objects dynamically.

Example CSS Styles:

  1. Basic Mesh Styling:

    mesh {
        color: #ff0000; /* Red color */
        opacity: 0.75;
    }
    
  2. Textured Geometry:

    textured-mesh {
        texture: url('textures/wood.jpg');
        repeat: 2 2;
    }
    
  3. Box Geometry Dimensions:

    box {
        width: 100;
        height: 50;
        depth: 50;
    }
    
  4. Transformations:

    mesh {
        position: 0 100 0;
        rotation: 45 45 0; /* Degrees */
    }
    
  5. Animation:

    @keyframes rotate {
        from { rotation: 0 0 0; }
        to { rotation: 360 360 0; }
    }
    
    rotating-mesh {
        animation: rotate 10s infinite linear;
    }
    

Seeking Feedback:

  • Community Input: I’m looking for feedback, suggestions, and ideas from the Three.js community.
  • Use Cases: Interested in hearing how you might use such a feature in your projects.

I believe this project can make 3D styling more intuitive and familiar for web developers, potentially opening up new creative possibilities in Three.js development.

2 Likes

Depends - which part of webdev community you’re targeting.

Taking into account that devs are spending their nights trying to figure out ways for us to move away as far as possible from CSS - css-in-js, then styled-components, then tailwind - the odds are not in favor in this case :smiling_face_with_tear:

Additionally - it’d need to be made somehow compatible with react-three-fiber (most likely in the styled-components-like template literals form) for it to be adopted within any commercial project, since pretty much all of them use r3f instead of vanilla.

Thanks for your feedback. I was not aware of css-in-js. It seems like a much better approach to styling since it is state driven and could be text based when needed since its just a JSON object.

All of the material types support a setValues method (which matches the parameters included in the constructor). This could be use to set materials style values.

However, none of the geometry types support this, so wrappers would need to be created.

Given this, it should be possible to build APIs that apply styles by matching objects in the scene.

Here’s an example of a wrapped BoxGeometry that supports dynamically changing the settings

interface BoxGeometrySettings {
  width: number;
  height: number;
  depth: number;
  widthSegments?: number;
  heightSegments?: number;
  depthSegments?: number;
}

class BoxGeometryWrapper extends BoxGeometry {
  constructor(settings?: Partial<BoxGeometrySettings>) {
    super(settings?.width, settings?.height, settings?.depth, settings?.widthSegments, settings?.heightSegments, settings?.depthSegments);
  }

  get width(): number {
    return this.parameters.width;
  }

  set width(value: number) {
    this.updateGeometry(value, this.height, this.depth, this.widthSegments, this.heightSegments, this.depthSegments);
  }

  get height(): number {
    return this.parameters.height;
  }

  set height(value: number) {

    this.updateGeometry(this.width, value, this.depth, this.widthSegments, this.heightSegments, this.depthSegments);
  }

  get depth(): number {
    return this.parameters.depth;
  }

  set depth(value: number) {

    this.updateGeometry(this.width, this.height, value, this.widthSegments, this.heightSegments, this.depthSegments);
  }

  get widthSegments(): number {
    return this.parameters.widthSegments;
  }

  set widthSegments(value: number) {

    this.updateGeometry(this.width, this.height, this.depth, value, this.heightSegments, this.depthSegments);
  }

  get heightSegments(): number {
    return this.parameters.heightSegments;
  }

  set heightSegments(value: number) {

    this.updateGeometry(this.width, this.height, this.depth, this.widthSegments, value, this.depthSegments);
  }


  get depthSegments(): number {
    return this.parameters.depthSegments;
  }

  set depthSegments(value: number) {

    this.updateGeometry(this.width, this.height, this.depth, this.widthSegments, this.heightSegments, value);
  }

  setValues(values: Partial<BoxGeometrySettings>) {
    this.updateGeometry(values.width ?? this.width,
      values.height ?? this.height,
      values.depth ?? this.depth,
      values.widthSegments ?? this.widthSegments,
      values.heightSegments ?? this.heightSegments,
      values.depthSegments ?? this.depthSegments);
  }

  private updateGeometry(width: number, height: number, depth: number, widthSegments: number, heightSegments: number, depthSegments: number) {
    const newGeometry = new BoxGeometry(
      width,
      height,
      depth,
      widthSegments,
      heightSegments,
      depthSegments
    );
    this.copy(newGeometry);

  }
}