<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.169.0/build/three.module.js",
      "three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
    }
  }
</script>
body{
  overflow: hidden;
  margin: 0;
}
// https://discourse.threejs.org/t/moving-road-effect-using-a-normal-map-similar-to-car-materials-example/72879/8

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

console.clear();

let scene = new THREE.Scene();
scene.background = new THREE.Color("#440000");
let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0.5, 1).setLength(10);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);

window.addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let light = new THREE.DirectionalLight(0xffffff, Math.PI);
light.castShadow = true;
light.shadow.mapSize.width = 1024; 
light.shadow.mapSize.height = 1024;
light.shadow.intensity = Math.PI * 0.5;
light.position.setScalar(5);
scene.add(light, new THREE.AmbientLight(0xffffff, Math.PI * 0.5));

let road = new THREE.Mesh(
  new THREE.PlaneGeometry(10, 10, 2, 2).rotateX(-Math.PI * 0.5),
  new THREE.MeshLambertMaterial({
    //fog: false,
    map: new THREE.TextureLoader().load(
      "https://threejs.org/examples/textures/hardwood2_diffuse.jpg",
      (tex) => {
        tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
        tex.colorSpace = "srgb";
        tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
      }
    ),
    onBeforeCompile: (shader) => {
      shader.uniforms.distMin = { value: 0.25 };
      shader.uniforms.distMax = { value: 1 };
      shader.uniforms.distCol = { value: scene.background };
      //console.log(shader.vertexShader);
      shader.fragmentShader = `
        uniform float distMin;
        uniform float distMax;
        uniform vec3 distCol;
        ${shader.fragmentShader}
      `.replace(
        `#include <tonemapping_fragment>`,
        `#include <tonemapping_fragment>
        
          gl_FragColor.rgb = mix(gl_FragColor.rgb, distCol, smoothstep(distMin, distMax, length(vUv - 0.5) * 2.));
        `
      );
      //console.log(shader.fragmentShader);
    }
  })
);
road.material.defines = { USE_UV: "" };
road.receiveShadow = true;
scene.add(road);

let shapeWheel = new THREE.Shape().absarc(0, 0, 1.5, 0, Math.PI * 2);
shapeWheel.holes = Array.from({ length: 6 }, (_, hIdx) => {
  return new THREE.Path(
    [
      [1.3, 0.5],
      [1.3, -0.5],
      [0.3, -0.1],
      [0.3, 0.1]
    ].map((p) => {
      return new THREE.Vector2(...p).rotateAround(
        new THREE.Vector2(),
        (Math.PI * 2 / 6) * hIdx
      );
    })
  );
});
let wheel = new THREE.Mesh(
  new THREE.ExtrudeGeometry(shapeWheel, {
    depth: 0.5,
    curveSegments: 20,
    bevelEnabled: false
  }).center(),
  new THREE.MeshLambertMaterial({
    color: "brown",
    map: new THREE.TextureLoader().load(
      "https://threejs.org/examples/textures/hardwood2_diffuse.jpg",
      (tex) => {
        tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
        tex.colorSpace = "srgb";
        tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
      }
    )
  })
);
wheel.material.wrapT = wheel.material.wrapS = THREE.RepeatWrapping;
wheel.position.set(0, 1.5, 0);
wheel.castShadow = true;
scene.add(wheel);

let clock = new THREE.Clock();
let t = 0;

renderer.setAnimationLoop(() => {
  let dt = clock.getDelta();
  t += dt;
  controls.update();
  road.material.map.offset.x = t * 0.1;
  
  // speed = 10 * 0.1 = 1 unit / sec
  // angular speed = speed / radius = 1 / 1.5
  wheel.rotation.z = -(1 / 1.5) * t;
  
  renderer.render(scene, camera);
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.