three#Mesh TypeScript Examples

The following examples show how to use three#Mesh. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ParticleSystem.tsx    From react-ecs with MIT License 6 votes vote down vote up
RandomColorSystem: FC = () => {
    useQuery(e => e.has(ThreeView), {
        added: ({ current }) => {
            const view = current.get(ThreeView);
            if (view) {
                const mesh = view.ref.current as Mesh;
                const material = mesh.material as MeshBasicMaterial;
                material.color?.set(
                    new Color(Math.random(), Math.random(), Math.random())
                );
            }
        }
    });

    return null;
}
Example #2
Source File: Entities.tsx    From spacesvr with MIT License 6 votes vote down vote up
EntityAvatar = (props: EntityAvatarProps) => {
  const { uuid } = props;
  const { fetch } = useSimulation();

  const mesh = useRef<Mesh>();

  // retrieve player information every frame and update pos/rot
  useFrame(() => {
    const entityData = fetch("entities");
    const entity = entityData.get(uuid);

    if (mesh.current && entity) {
      // TODO: REMOVE MESHES ON DC
      // TODO: SNAPSHOT INTERPOLATION
      // TODO: ONLY UPDATE ON CHANGE

      mesh.current.position.fromArray(entity.position);
      mesh.current.rotation.fromArray(entity.rotation);
    }
  });

  return (
    <mesh name={`entity-${uuid}`} ref={mesh}>
      <cylinderBufferGeometry args={[0.3, 0.3, 1, 30]} />
      <meshNormalMaterial />
    </mesh>
  );
}
Example #3
Source File: MapView.d.ts    From geo-three with MIT License 6 votes vote down vote up
export declare class MapView extends Mesh {
    static PLANAR: number;
    static SPHERICAL: number;
    static HEIGHT: number;
    static HEIGHT_SHADER: number;
    static MARTINI: number;
    static mapModes: Map<number, any>;
    lod: LODControl;
    provider: MapProvider;
    heightProvider: MapProvider;
    root: MapNode;
    constructor(root?: (number | MapNode), provider?: MapProvider, heightProvider?: MapProvider);
    onBeforeRender: (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void;
    setRoot(root: (MapNode | number)): void;
    setProvider(provider: MapProvider): void;
    setHeightProvider(heightProvider: MapProvider): void;
    clear(): any;
    getMetaData(): void;
    raycast(raycaster: Raycaster, intersects: any[]): boolean;
}
Example #4
Source File: MapNode.d.ts    From geo-three with MIT License 6 votes vote down vote up
export declare abstract class MapNode extends Mesh {
    mapView: MapView;
    parentNode: MapNode;
    location: number;
    level: number;
    x: number;
    y: number;
    nodesLoaded: number;
    subdivided: boolean;
    childrenCache: Object3D[];
    cacheChild: boolean;
    isMesh: boolean;
    static baseGeometry: BufferGeometry;
    static baseScale: Vector3;
    static childrens: number;
    static root: number;
    static topLeft: number;
    static topRight: number;
    static bottomLeft: number;
    static bottomRight: number;
    constructor(parentNode?: MapNode, mapView?: MapView, location?: number, level?: number, x?: number, y?: number, geometry?: BufferGeometry, material?: Material);
    initialize(): void;
    createChildNodes(): void;
    subdivide(): void;
    simplify(): void;
    loadTexture(): Promise<void>;
    nodeReady(): void;
    getNeighborsDirection(direction: number): MapNode[];
    getNeighbors(): MapNode[];
}
Example #5
Source File: BoidExample.tsx    From react-ecs with MIT License 5 votes vote down vote up
BoidExample = (props) => {
    const ECS = useECS();

    useAnimationFrame(ECS.update)

    const { nodes } = useGLTF(LOGO_PATH);
    const geometry = (nodes.Regular_Plane as Mesh).geometry;

    return (
            <Canvas>
                <Suspense fallback={<Torus />}>
                    <ECS.Provider>
                        <fog attach="fog" args={['black', 25, 250]} />
                        <ambientLight intensity={.3} />
                        <directionalLight
                            color='red'
                            intensity={3}
                        />
                        <OrbitControls
                            enablePan
                            enableRotate
                            enableZoom
                            minDistance={80}
                            maxDistance={200}
                            maxPolarAngle={1.5}
                            {...props.cameraProps}
                        />

                        <BoidSim render={() =>
                            <mesh
                                castShadow
                                receiveShadow
                                geometry={geometry}>
                                <meshNormalMaterial />
                            </mesh>
                        } />
                    </ECS.Provider>
                </Suspense>
            </Canvas>
    )
}
Example #6
Source File: BoidScene.tsx    From react-ecs with MIT License 5 votes vote down vote up
BoidScene: FC = (props) => {
    const { nodes } = useGLTF(LOGO_PATH);
    const geometry = (nodes.Regular_Plane as Mesh).geometry;

    const { fogNear, fogFar } = useControls('fog', {
        fogNear: {
            value: 25,
            min: 10,
            max: 150,
        },
        fogFar: {
            value: 250,
            min: 30,
            max: 300,
        },
    })

    const { autoSpin, spinSpeed } = useControls('camera', {
        autoSpin: true,
        spinSpeed: {
            value: .5,
            min: 0,
            max: 3,
        },
    })

    return (
        <>
            <fog attach="fog" args={['black', fogNear, fogFar]} />
            <ambientLight intensity={.3} />
            <directionalLight
                color='red'
                intensity={3}
            />
            <OrbitControls
                enablePan
                enableRotate
                enableZoom={false}
                autoRotate={autoSpin}
                autoRotateSpeed={spinSpeed}
                minDistance={80}
                maxDistance={280}
                maxPolarAngle={1.5}
            />

            <BoidSim render={() =>
                <mesh
                    castShadow
                    receiveShadow
                    geometry={geometry}>
                    <meshPhongMaterial />
                </mesh>
            } />
        </>
    )
}
Example #7
Source File: use-softbody.tsx    From use-ammojs with MIT License 5 votes vote down vote up
export function useSoftBody(
  options: UseSoftBodyOptions | (() => UseSoftBodyOptions),
  mesh?: Mesh
): [MutableRefObject<Mesh | undefined>, SoftbodyApi] {
  const ref = useRef<Mesh>();

  const physicsContext = useAmmoPhysicsContext();
  const { addSoftBody, removeSoftBody } = physicsContext;

  const [bodyUUID] = useState(() => MathUtils.generateUUID());

  useEffect(() => {
    const meshToUse = mesh ? mesh : ref.current!;

    if (typeof options === "function") {
      options = options();
    }

    const { anchors, ...rest } = options;

    if (!meshToUse) {
      throw new Error("useSoftBody ref does not contain a mesh");
    }

    addSoftBody(bodyUUID, meshToUse, {
      anchors:
        anchors &&
        anchors.map((anchor) => {
          if (isSoftBodyRigidBodyAnchorRef(anchor)) {
            const { rigidBodyRef, ...anchorProps } = anchor;

            return {
              ...anchorProps,
              rigidBodyUUID:
                anchor.rigidBodyRef.current?.userData?.useAmmo?.rigidBody?.uuid,
            };
          }
          return anchor;
        }),
      ...rest,
    });

    return () => {
      removeSoftBody(bodyUUID);
    };
  }, []);

  return [ref, createSoftbodyApi(physicsContext, bodyUUID)];
}
Example #8
Source File: Text.tsx    From spacesvr with MIT License 5 votes vote down vote up
export function Text(props: TextProps) {
  const {
    text,
    vAlign = "center",
    hAlign = "center",
    size = 1,
    bevel = false,
    color = "#000000",
    font: fontFile,
    material,
    ...rest
  } = props;

  const font = useLoader(FontLoader, fontFile || FONT_FILE);
  const mesh = useRef<Mesh>();

  const config = useMemo(
    () => ({
      font,
      size,
      height: 0.75 * size,
      curveSegments: 32,
      bevelEnabled: bevel,
      bevelThickness: 0.15 * size,
      bevelSize: 0.0625 * size,
      bevelOffset: 0,
      bevelSegments: 8,
    }),
    [font]
  );

  useEffect(() => {
    if (!mesh.current) return;

    const size = new Vector3();
    mesh.current.geometry.computeBoundingBox();
    mesh.current.geometry?.boundingBox?.getSize(size);
    mesh.current.position.x =
      hAlign === "center" ? -size.x / 2 : hAlign === "right" ? 0 : -size.x;
    mesh.current.position.y =
      vAlign === "center" ? -size.y / 2 : vAlign === "top" ? 0 : -size.y;
  }, [text]);

  return (
    <group name="spacesvr-text" {...rest} scale={[0.1 * size, 0.1 * size, 0.1]}>
      <mesh ref={mesh} material={material}>
        <textGeometry attach="geometry" args={[text, config]} />
        {!material && (
          <meshPhongMaterial
            attach="material"
            color={color}
            reflectivity={30}
          />
        )}
      </mesh>
    </group>
  );
}
Example #9
Source File: physics-provider.tsx    From use-ammojs with MIT License 4 votes vote down vote up
export function Physics({
  drawDebug,
  drawDebugMode = DEFAULT_DEBUG_MODE,
  gravity,
  epsilon,
  fixedTimeStep,
  maxSubSteps,
  solverIterations,
  simulationSpeed = 1,
  children,
}: PropsWithChildren<AmmoPhysicsProps>) {
  const [physicsState, setPhysicsState] = useState<PhysicsState>();

  const sharedBuffersRef = useRef<SharedBuffers>({} as any);

  // Functions that are executed while the main thread holds control over the shared data
  const threadSafeQueueRef = useRef<(() => void)[]>([]);

  const physicsPerformanceInfoRef = useRef<PhysicsPerformanceInfo>({
    substepCounter: 0,
    lastTickMs: 0,
  });

  useEffect(() => {
    const uuids: string[] = [];
    const object3Ds: Record<string, Object3D> = {};
    const uuidToIndex: Record<string, number> = {};
    const IndexToUuid: Record<number, string> = {};
    const bodyOptions: Record<string, BodyConfig> = {};

    const softBodies: Record<UUID, Mesh> = {};

    const ammoWorker: Worker = createAmmoWorker();

    const workerHelpers = WorkerHelpers(ammoWorker);

    const rigidBodyBuffer = allocateCompatibleBuffer(
      4 * BUFFER_CONFIG.HEADER_LENGTH + //header
        4 * BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES + //matrices
        4 * BUFFER_CONFIG.MAX_BODIES //velocities
    );
    const headerIntArray = new Int32Array(
      rigidBodyBuffer,
      0,
      BUFFER_CONFIG.HEADER_LENGTH
    );
    const headerFloatArray = new Float32Array(
      rigidBodyBuffer,
      0,
      BUFFER_CONFIG.HEADER_LENGTH
    );
    const objectMatricesIntArray = new Int32Array(
      rigidBodyBuffer,
      BUFFER_CONFIG.HEADER_LENGTH * 4,
      BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES
    );
    const objectMatricesFloatArray = new Float32Array(
      rigidBodyBuffer,
      BUFFER_CONFIG.HEADER_LENGTH * 4,
      BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES
    );

    objectMatricesIntArray[0] = BufferState.UNINITIALIZED;

    const debugBuffer = allocateCompatibleBuffer(4 + 2 * DefaultBufferSize * 4);
    const debugIndex = new Uint32Array(debugBuffer, 0, 4);
    const debugVertices = new Float32Array(debugBuffer, 4, DefaultBufferSize);
    const debugColors = new Float32Array(
      debugBuffer,
      4 + DefaultBufferSize,
      DefaultBufferSize
    );
    const debugGeometry = new BufferGeometry();
    debugGeometry.setAttribute(
      "position",
      new BufferAttribute(debugVertices, 3).setUsage(DynamicDrawUsage)
    );
    debugGeometry.setAttribute(
      "color",
      new BufferAttribute(debugColors, 3).setUsage(DynamicDrawUsage)
    );

    sharedBuffersRef.current = {
      rigidBodies: {
        headerIntArray,
        headerFloatArray,
        objectMatricesFloatArray,
        objectMatricesIntArray,
      },

      softBodies: [],

      debug: {
        indexIntArray: debugIndex,
        vertexFloatArray: debugVertices,
        colorFloatArray: debugColors,
      },
    };

    const worldConfig: WorldConfig = {
      debugDrawMode: ammoDebugOptionsToNumber(drawDebugMode),
      gravity: gravity && new Vector3(gravity[0], gravity[1], gravity[2]),
      epsilon,
      fixedTimeStep,
      maxSubSteps,
      solverIterations,
    };

    workerHelpers.initWorld(worldConfig, sharedBuffersRef.current);

    const workerInitPromise = new Promise<PhysicsState>((resolve) => {
      ammoWorker.onmessage = async (event) => {
        const type: ClientMessageType = event.data.type;

        switch (type) {
          case ClientMessageType.READY: {
            if (event.data.sharedBuffers) {
              sharedBuffersRef.current = event.data.sharedBuffers;
            }

            resolve({
              workerHelpers,
              sharedBuffersRef,
              debugGeometry,
              debugBuffer,
              bodyOptions,
              uuids,
              object3Ds,
              softBodies,
              uuidToIndex,
              debugIndex,
              addRigidBody,
              removeRigidBody,
              addSoftBody,
              removeSoftBody,
              rayTest,
            });
            return;
          }
          case ClientMessageType.RIGIDBODY_READY: {
            const uuid = event.data.uuid;
            uuids.push(uuid);
            uuidToIndex[uuid] = event.data.index;
            IndexToUuid[event.data.index] = uuid;
            return;
          }
          case ClientMessageType.SOFTBODY_READY: {
            threadSafeQueueRef.current.push(() => {
              sharedBuffersRef.current.softBodies.push(
                event.data.sharedSoftBodyBuffers
              );
            });
            return;
          }
          case ClientMessageType.TRANSFER_BUFFERS: {
            sharedBuffersRef.current = event.data.sharedBuffers;
            return;
          }
          case ClientMessageType.RAYCAST_RESPONSE: {
            workerHelpers.resolveAsyncRequest(event.data);
            return;
          }
        }
        throw new Error("unknown message type" + type);
      };
    });

    workerInitPromise.then(setPhysicsState);

    function addRigidBody(
      uuid,
      mesh,
      shape: ShapeDescriptor,
      options: BodyConfig = {}
    ) {
      bodyOptions[uuid] = options;
      object3Ds[uuid] = mesh;

      if (!mesh.userData.useAmmo) {
        mesh.userData.useAmmo = {};
      }

      mesh.userData.useAmmo.rigidBody = {
        uuid,
      };

      workerHelpers.addRigidBody(uuid, mesh, shape, options);
    }

    function removeRigidBody(uuid: string) {
      uuids.splice(uuids.indexOf(uuid), 1);
      delete IndexToUuid[uuidToIndex[uuid]];
      delete uuidToIndex[uuid];
      delete bodyOptions[uuid];
      delete object3Ds[uuid].userData.useAmmo.rigidBody;
      delete object3Ds[uuid];
      workerHelpers.removeRigidBody(uuid);
    }

    function addSoftBody(uuid: UUID, mesh: Mesh, options: SoftBodyConfig = {}) {
      if (!mesh.geometry) {
        console.error("useSoftBody received: ", mesh);
        throw new Error("useSoftBody is only supported on BufferGeometries");
      }

      let indexLength: number;
      let vertexLength: number;
      let normalLength: number;

      switch (options.type) {
        case SoftBodyType.TRIMESH:
          // console.log("before merge ", mesh.geometry.attributes.position.count);
          mesh.geometry.deleteAttribute("normal");
          mesh.geometry.deleteAttribute("uv");
          mesh.geometry = mergeVertices(mesh.geometry);
          mesh.geometry.computeVertexNormals();
          // console.log("after merge ", mesh.geometry.attributes.position.count);

          indexLength =
            mesh.geometry.index!.count * mesh.geometry.index!.itemSize;
          vertexLength =
            mesh.geometry.attributes.position.count *
            mesh.geometry.attributes.position.itemSize;
          normalLength =
            mesh.geometry.attributes.normal.count *
            mesh.geometry.attributes.normal.itemSize;

          break;
        case SoftBodyType.ROPE:
          indexLength = 0;
          vertexLength =
            mesh.geometry.attributes.instanceStart.count *
            mesh.geometry.attributes.instanceStart.itemSize;
          normalLength = 0;

          break;
        default:
          throw new Error("unknown soft body type " + options.type);
      }

      const buffer = allocateCompatibleBuffer(
        indexLength * 4 + vertexLength * 4 + normalLength * 4
      );

      const sharedSoftBodyBuffers: SharedSoftBodyBuffers = {
        uuid,
        indexIntArray: new (indexLength > 65535 ? Uint32Array : Uint16Array)(
          buffer,
          0,
          indexLength
        ),
        vertexFloatArray: new Float32Array(
          buffer,
          indexLength * 4,
          vertexLength
        ),
        normalFloatArray: new Float32Array(
          buffer,
          indexLength * 4 + vertexLength * 4,
          normalLength
        ),
      };

      // Bullet softbodies operate in world-space,
      // so the transform needs to be baked into the vertex data
      mesh.updateMatrixWorld(true);
      mesh.geometry.applyMatrix4(mesh.matrixWorld);

      mesh.position.set(0, 0, 0);
      mesh.quaternion.set(0, 0, 0, 1);
      mesh.scale.set(1, 1, 1);

      mesh.frustumCulled = false;

      if (options.type === SoftBodyType.TRIMESH) {
        sharedSoftBodyBuffers.vertexFloatArray.set(
          mesh.geometry.attributes.position.array
        );

        sharedSoftBodyBuffers.indexIntArray.set(mesh.geometry.index!.array);
        sharedSoftBodyBuffers.normalFloatArray.set(
          mesh.geometry.attributes.normal.array
        );
      } else {
        for (let i = 0; i < vertexLength; i++) {
          sharedSoftBodyBuffers.vertexFloatArray[
            i * 3
          ] = mesh.geometry.attributes.instanceStart.getX(i);
          sharedSoftBodyBuffers.vertexFloatArray[
            i * 3 + 1
          ] = mesh.geometry.attributes.instanceStart.getY(i);
          sharedSoftBodyBuffers.vertexFloatArray[
            i * 3 + 2
          ] = mesh.geometry.attributes.instanceStart.getZ(i);
        }
      }

      if (isSharedArrayBufferSupported) {
        if (options.type === SoftBodyType.TRIMESH) {
          mesh.geometry.setAttribute(
            "position",
            new BufferAttribute(
              sharedSoftBodyBuffers.vertexFloatArray,
              3
            ).setUsage(DynamicDrawUsage)
          );

          mesh.geometry.setAttribute(
            "normal",
            new BufferAttribute(
              sharedSoftBodyBuffers.normalFloatArray,
              3
            ).setUsage(DynamicDrawUsage)
          );
        }
      }

      softBodies[uuid] = mesh;

      workerHelpers.addSoftBody(uuid, sharedSoftBodyBuffers, options);
    }

    function removeSoftBody(uuid: string) {
      delete softBodies[uuid];
      workerHelpers.removeSoftBody(uuid);

      sharedBuffersRef.current.softBodies = sharedBuffersRef.current.softBodies.filter(
        (ssbb) => ssbb.uuid !== uuid
      );
    }

    async function rayTest(options: RaycastOptions): Promise<RaycastHit[]> {
      const { hits } = await workerHelpers.makeAsyncRequest({
        type: MessageType.RAYCAST_REQUEST,
        ...options,
      });

      return hits.map(
        (hit: RaycastHitMessage): RaycastHit => {
          return {
            object: object3Ds[hit.uuid] || softBodies[hit.uuid],

            hitPosition: new Vector3(
              hit.hitPosition.x,
              hit.hitPosition.y,
              hit.hitPosition.z
            ),

            normal: new Vector3(hit.normal.x, hit.normal.y, hit.normal.z),
          };
        }
      );
    }

    return () => {
      ammoWorker.terminate();
      setPhysicsState(undefined);
    };
  }, []);

  useEffect(() => {
    if (!isSharedArrayBufferSupported) {
      if (drawDebug) {
        console.warn("debug visuals require SharedArrayBuffer support");
      }
      return;
    }

    if (physicsState) {
      if (drawDebug) {
        workerHelpers.enableDebug(true, physicsState.debugBuffer);
      } else {
        workerHelpers.enableDebug(false, physicsState.debugBuffer);
      }
    }
  }, [drawDebug, physicsState]);

  useEffect(() => {
    if (physicsState?.workerHelpers) {
      workerHelpers.setSimulationSpeed(simulationSpeed);
    }
  }, [physicsState?.workerHelpers, simulationSpeed]);

  if (!physicsState) {
    return null;
  }

  const { workerHelpers, debugGeometry } = physicsState;

  return (
    <AmmoPhysicsContext.Provider
      value={{
        ...workerHelpers,

        // workerHelpers Overrides
        addRigidBody: physicsState.addRigidBody,
        removeRigidBody: physicsState.removeRigidBody,

        addSoftBody: physicsState.addSoftBody,
        removeSoftBody: physicsState.removeSoftBody,

        object3Ds: physicsState.object3Ds,

        rayTest: physicsState.rayTest,

        physicsPerformanceInfoRef,
      }}
    >
      <PhysicsUpdate
        {...{
          physicsState,
          sharedBuffersRef,
          threadSafeQueueRef,
          physicsPerformanceInfoRef,
        }}
      />
      {drawDebug && <PhysicsDebug geometry={debugGeometry} />}
      {children}
    </AmmoPhysicsContext.Provider>
  );
}
Example #10
Source File: TextMeshObject.ts    From movy with MIT License 4 votes vote down vote up
updateText() {
    if (this.shouldUpdate) {
      // TODO: optimize: text update is slow.
      this.children.length = 0;

      let totalWidth = 0;
      const letterPosX: number[] = [];
      let minY = Number.MAX_VALUE;
      let maxY = Number.MIN_VALUE;
      const geometries: ShapeBufferGeometry[] = [];
      for (const [i, char] of [...this.text].entries()) {
        if (char === ' ') {
          totalWidth += this.initParams.fontSize * 0.5;
        } else {
          let font: Font;
          let glyph: any;
          for (let j = 0; j < this.fonts.length; j++) {
            font = this.fonts[j];
            glyph = (font.data as any).glyphs[char];
            if (glyph) {
              break;
            } else if (j == this.fonts.length - 1) {
              glyph = (font.data as any).glyphs['?'];
            }
          }

          const fontData = font.data as any;
          const resolution = fontData.resolution;
          const ha = (glyph.ha / resolution) * this.initParams.fontSize;

          const shapes = font.generateShapes(char, this.initParams.fontSize);

          let geometry;
          if (this.initParams.text3D) {
            const extrudeSettings = {
              depth: this.initParams.fontSize * 0.2,
              bevelEnabled: false,
            };
            geometry = new ExtrudeGeometry(shapes, extrudeSettings);
          } else if (this.initParams.stroke) {
            const style = SVGLoader.getStrokeStyle(
              this.initParams.strokeWidth,
              this.initParams.color.getStyle() // color in CSS context style
            );
            // Add shape.holes to shapes
            const holeShapes = [];
            for (let i = 0; i < shapes.length; i++) {
              const shape = shapes[i];
              if (shape.holes && shape.holes.length > 0) {
                for (let j = 0; j < shape.holes.length; j++) {
                  const hole = shape.holes[j];
                  holeShapes.push(hole);
                }
              }
            }
            shapes.push.apply(shapes, holeShapes);

            const geoms: BufferGeometry[] = [];
            for (const shape of shapes) {
              const points = shape.getPoints();
              const geom = SVGLoader.pointsToStroke(
                points.map((v) => new Vector3(v.x, v.y)),
                style
              );
              geoms.push(geom);
            }
            geometry = geoms.length > 1 ? mergeBufferGeometries(geoms) : geoms[0];
          } else {
            geometry = new ShapeBufferGeometry(shapes);
          }

          geometry.computeBoundingBox();

          geometries.push(geometry);

          // Always create a separate material for each letter
          const mesh = new Mesh(geometry, this.material.clone());
          mesh.name = char;

          const letterWidth = ha;
          const xMid = 0.5 * letterWidth;
          geometry.translate(
            -0.5 * (geometry.boundingBox.min.x + geometry.boundingBox.max.x),
            -0.5 * this.initParams.fontSize,
            0
          );

          letterPosX.push(totalWidth + xMid);
          totalWidth +=
            letterWidth +
            (i < this.text.length - 1
              ? this.initParams.letterSpacing * this.initParams.fontSize
              : 0);
          minY = Math.min(minY, geometry.boundingBox.min.y);
          maxY = Math.max(maxY, geometry.boundingBox.max.y);

          this.add(mesh);
        }
      }

      // Center text geometry vertically
      const deltaY = (maxY + minY) * 0.5;
      if (this.initParams.centerTextVertically) {
        for (const geometry of geometries) {
          geometry.translate(0, -deltaY, 0);
        }
      }

      this.children.forEach((letter, i) => {
        letter.position.set(-0.5 * totalWidth + letterPosX[i], 0, 0);
      });

      this.shouldUpdate = false;
    }
  }
Example #11
Source File: MapView.ts    From geo-three with MIT License 4 votes vote down vote up
/**
 * Map viewer is used to read and display map tiles from a server.
 *
 * It was designed to work with a OpenMapTiles but can also be used with another map tiles.
 *
 * The map is drawn in plane map nodes using a quad tree that is subdivided as necessary to guaratee good map quality.
 */
export class MapView extends Mesh 
{
	/**
	 * Planar map projection.
	 */
	public static PLANAR: number = 200;

	/**
	 * Spherical map projection.
	 */
	public static SPHERICAL: number = 201;

	/**
	 * Planar map projection with height deformation.
	 */
	public static HEIGHT: number = 202;

	/**
	 * Planar map projection with height deformation using the GPU for height generation.
	 */
	public static HEIGHT_SHADER: number = 203;

	/**
	 * RTIN map mode.
	 */
	public static MARTINI: number = 204;

	/**
	 * Map of the map node types available.
	 */
	public static mapModes: Map<number, any> = new Map<number, any>([
		[MapView.PLANAR, MapPlaneNode],
		[MapView.SPHERICAL, MapSphereNode],
		[MapView.HEIGHT, MapHeightNode],
		[MapView.HEIGHT_SHADER, MapHeightNodeShader],
		[MapView.MARTINI, MapMartiniHeightNode]
	]);

	/**
	 * LOD control object used to defined how tiles are loaded in and out of memory.
	 */
	public lod: LODControl = null;

	/**
	 * Map tile color layer provider.
	 */
	public provider: MapProvider = null;

	/**
	 * Map height (terrain elevation) layer provider.
	 */
	public heightProvider: MapProvider = null;

	/**
	 * Define the type of map node in use, defined how the map is presented.
	 *
	 * Should only be set on creation.
	 */
	public root: MapNode = null;

	/**
	 * Constructor for the map view objects.
	 *
	 * @param root - Map view node modes can be SPHERICAL, HEIGHT or PLANAR. PLANAR is used by default. Can also be a custom MapNode instance.
	 * @param provider - Map color tile provider by default a OSM maps provider is used if none specified.
	 * @param heightProvider - Map height tile provider, by default no height provider is used.
	 */
	public constructor(root: (number | MapNode) = MapView.PLANAR, provider: MapProvider = new OpenStreetMapsProvider(), heightProvider: MapProvider = null) 
	{
		super(undefined, new MeshBasicMaterial({transparent: true, opacity: 0.0}));

		this.lod = new LODRaycast();

		this.provider = provider;
		this.heightProvider = heightProvider;

		this.setRoot(root);
	}

	/**
	 * Ajust node configuration depending on the camera distance.
	 *
	 * Called everytime before render.
	 */
	public onBeforeRender: (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group)=> void = (renderer, scene, camera, geometry, material, group) => 
	{
		this.lod.updateLOD(this, camera, renderer, scene);
	};

	/**
	 * Set the root of the map view.
	 *
	 * Is set by the constructor by default, can be changed in runtime.
	 *
	 * @param root - Map node to be used as root.
	 */
	public setRoot(root: (MapNode | number)): void
	{
		if (typeof root === 'number') 
		{
			if (!MapView.mapModes.has(root)) 
			{
				throw new Error('Map mode ' + root + ' does is not registered.');
			}

			const rootConstructor = MapView.mapModes.get(root);

			// @ts-ignore
			root = new rootConstructor(null, this);
		}

		// Remove old root
		if (this.root !== null) 
		{
			this.remove(this.root);
			this.root = null;
		}

		// @ts-ignore
		this.root = root;

		// Initialize root node
		if (this.root !== null) 
		{
			// TODO <REMOVE THIS>
			console.log(this.root);

			// @ts-ignore
			this.geometry = this.root.constructor.baseGeometry;

			// @ts-ignore
			this.scale.copy(this.root.constructor.baseScale);

			this.root.mapView = this;
			this.add(this.root);
		}
	}

	/**
	 * Change the map provider of this map view.
	 *
	 * Will discard all the tiles already loaded using the old provider.
	 */
	public setProvider(provider: MapProvider): void
	{
		if (provider !== this.provider) 
		{
			this.provider = provider;
			this.clear();
		}
	}

	/**
	 * Change the map height provider of this map view.
	 *
	 * Will discard all the tiles already loaded using the old provider.
	 */
	public setHeightProvider(heightProvider: MapProvider): void
	{
		if (heightProvider !== this.heightProvider) 
		{
			this.heightProvider = heightProvider;
			this.clear();
		}
	}

	/**
	 * Clears all tiles from memory and reloads data. Used when changing the provider.
	 *
	 * Should be called manually if any changed to the provider are made without setting the provider.
	 */
	public clear(): any
	{
		this.traverse(function(children: Object3D): void
		{
			// @ts-ignore
			if (children.childrenCache) 
			{
				// @ts-ignore
				children.childrenCache = null;
			}

			// @ts-ignore
			if (children.initialize) 
			{
				// @ts-ignore
				children.initialize();
			}
		});

		return this;
	}

	/**
	 * Get map meta data from server if supported.
	 */
	public getMetaData(): void
	{
		this.provider.getMetaData();
	}

	public raycast(raycaster: Raycaster, intersects: any[]): boolean
	{
		return false;
	}
}
Example #12
Source File: MapNode.ts    From geo-three with MIT License 4 votes vote down vote up
/**
 * Represents a map tile node inside of the tiles quad-tree
 *
 * Each map node can be subdivided into other nodes.
 *
 * It is intended to be used as a base class for other map node implementations.
 */
export abstract class MapNode extends Mesh 
{
	/**
	 * The map view object where the node is placed.
	 */
	public mapView: MapView = null;

	/**
	 * Parent node (from an upper tile level).
	 */
	public parentNode: MapNode = null;

	/**
	 * Index of the map node in the quad-tree parent node.
	 *
	 * Position in the tree parent, can be topLeft, topRight, bottomLeft or bottomRight.
	 */
	public location: number;

	/**
	 * Tile level of this node.
	 */
	public level: number;

	/**
	 * Tile x position.
	 */
	public x: number;

	/**
	 * Tile y position.
	 */
	public y: number;

	/**
	 * Indicates how many children nodes where loaded.
	 */
	public nodesLoaded: number = 0;

	/**
	 * Variable to check if the node is subdivided.
	 *
	 * To avoid bad visibility changes on node load.
	 */
	public subdivided: boolean = false;

	/**
	 * Cache with the children objects created from subdivision.
	 *
	 * Used to avoid recreate object after simplification and subdivision.
	 *
	 * The default value is null. Only used if "cacheChild" is set to true.
	 */
	public childrenCache: Object3D[] = null;

	/**
	 * Indicate if the node should cache its children when it is simplified.
	 * 
	 * Should only be used if the child generation process is time consuming.
	 */
	public cacheChild: boolean = false;

	/**
	 * Variable to check if the node is a mesh.
	 *
	 * Used to draw or not draw the node
	 */
	// @ts-ignore
	public isMesh: boolean = true;

	/**
	 * Base geometry is attached to the map viewer object.
	 *
	 * It should have the full size of the world so that operations over the MapView bounding box/sphere work correctly.
	 */
	public static baseGeometry: BufferGeometry = null;

	/**
	 * Base scale applied to the map viewer object.
	 */
	public static baseScale: Vector3 = null;
 
	/**
	 * How many children each branch of the tree has.
	 *
	 * For a quad-tree this value is 4.
	 */
	public static childrens: number = 4;
 
	/**
	 * Root node has no location.
	 */
	public static root: number = -1;
 
	/**
	 * Index of top left quad-tree branch node.
	 *
	 * Can be used to navigate the children array looking for neighbors.
	 */
	public static topLeft: number = 0;
 
	/**
	 * Index of top left quad-tree branch node.
	 *
	 * Can be used to navigate the children array looking for neighbors.
	 */
	public static topRight: number = 1;
 
	/**
	 * Index of top left quad-tree branch node.
	 *
	 * Can be used to navigate the children array looking for neighbors.
	 */
	public static bottomLeft: number = 2;
 
	/**
	 * Index of top left quad-tree branch node.
	 *
	 * Can be used to navigate the children array looking for neighbors.
	 */
	public static bottomRight: number = 3;

	public constructor(parentNode: MapNode = null, mapView: MapView = null, location: number = MapNode.root, level: number = 0, x: number = 0, y: number = 0, geometry: BufferGeometry = null, material: Material = null) 
	{
		super(geometry, material);

		this.mapView = mapView;
		this.parentNode = parentNode;
	

		this.location = location;
		this.level = level;
		this.x = x;
		this.y = y;

		this.initialize();
	}

	/**
	 * Initialize resources that require access to data from the MapView.
	 *
	 * Called automatically by the constructor for child nodes and MapView when a root node is attached to it.
	 */
	public initialize(): void {}

	/**
	 * Create the child nodes to represent the next tree level.
	 *
	 * These nodes should be added to the object, and their transformations matrix should be updated.
	 */
	public createChildNodes(): void {}

	/**
	 * Subdivide node,check the maximum depth allowed for the tile provider.
	 *
	 * Uses the createChildNodes() method to actually create the child nodes that represent the next tree level.
	 */
	public subdivide(): void
	{
		const maxZoom = Math.min(this.mapView.provider.maxZoom, this.mapView.heightProvider?.maxZoom ?? Infinity);
		if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens)
		{
			return;
		}

		this.subdivided = true;
		
		if (this.cacheChild && this.childrenCache !== null) 
		{
			this.isMesh = false;
			this.children = this.childrenCache;
		}
		else 
		{
			this.createChildNodes();
		}
	}

	/**
	 * Simplify node, remove all children from node, store them in cache.
	 *
	 * Reset the subdivided flag and restore the visibility.
	 *
	 * This base method assumes that the node implementation is based off Mesh and that the isMesh property is used to toggle visibility.
	 */
	public simplify(): void
	{
		if (this.cacheChild && this.children.length > 0) 
		{
			this.childrenCache = this.children;
		}
		
		this.subdivided = false;
		this.isMesh = true;
		this.children = [];
	}

	/**
	 * Load tile texture from the server.
	 *
	 * This base method assumes the existence of a material attribute with a map texture.
	 */
	public async loadTexture(): Promise<void>
	{
		try 
		{
			const image: HTMLImageElement = await this.mapView.provider.fetchTile(this.level, this.x, this.y);
		
			const texture = new Texture(image);
			texture.generateMipmaps = false;
			texture.format = RGBAFormat;
			texture.magFilter = LinearFilter;
			texture.minFilter = LinearFilter;
			texture.needsUpdate = true;
			
			// @ts-ignore
			this.material.map = texture;
			this.nodeReady();
		}
		catch (e) 
		{
			const canvas = CanvasUtils.createOffscreenCanvas(1, 1);
			const context = canvas.getContext('2d');
			context.fillStyle = '#FF0000';
			context.fillRect(0, 0, 1, 1);

			const texture = new Texture(canvas as any);
			texture.generateMipmaps = false;
			texture.needsUpdate = true;

			// @ts-ignore
			this.material.map = texture;
			this.nodeReady();
		}
	}

	/**
	 * Increment the child loaded counter.
	 *
	 * Should be called after a map node is ready for display.
	 */
	public nodeReady(): void
	{
		if (this.parentNode !== null) 
		{
			this.parentNode.nodesLoaded++;

			if (this.parentNode.nodesLoaded >= MapNode.childrens) 
			{
				if (this.parentNode.subdivided === true) 
				{
					this.parentNode.isMesh = false;
				}
				
				for (let i = 0; i < this.parentNode.children.length; i++) 
				{
					this.parentNode.children[i].visible = true;
				}
			}
		}
		// If its the root object just set visible
		else
		{
			this.visible = true;
		}
	}

	/**
	 * Get all the neighbors in a specific direction (left, right, up down).
	 *
	 * @param direction - Direction to get neighbors.
	 * @returns The neighbors array, if no neighbors found returns empty.
	 */
	public getNeighborsDirection(direction: number): MapNode[] 
	{
		// TODO <ADD CODE HERE>

		return null;
	}

	/**
	 * Get all the quad tree nodes neighbors. Are considered neighbors all the nodes directly in contact with a edge of this node.
	 *
	 * @returns The neighbors array, if no neighbors found returns empty.
	 */
	public getNeighbors(): MapNode[] 
	{
		const neighbors = [];

		// TODO <ADD CODE HERE>

		return neighbors;
	}
}