@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
206 lines (160 loc) • 6.56 kB
JavaScript
import { BufferGeometry, MeshBasicMaterial, MeshMatcapMaterial, MeshStandardMaterial } from "three";
import { assert } from "../../../../../../core/assert.js";
import { m3_rm_compose_transform } from "../../../../../../core/geom/mat3/m3_rm_compose_transform.js";
import { GameAssetType } from "../../../../../asset/GameAssetType.js";
import Entity from "../../../../../ecs/Entity.js";
import { Transform } from "../../../../../ecs/transform/Transform.js";
import { InterpolationType } from "../../../../../navigation/ecs/components/InterpolationType.js";
import { ShadedGeometry } from "../../../mesh-v2/ShadedGeometry.js";
import { ShadedGeometryFlags } from "../../../mesh-v2/ShadedGeometryFlags.js";
import { TubeMaterialType } from "../TubeMaterialType.js";
import { build_geometry_catmullrom } from "./build_geometry_catmullrom.js";
import { build_geometry_linear } from "./build_geometry_linear.js";
import { compute_smooth_profile_normals } from "./compute_smooth_profile_normals.js";
import { fix_shape_normal_order } from "./fix_shape_normal_order.js";
/**
*
* @param {number} segment_start
* @param {number} segment_end
* @returns {BufferGeometry}
* @param {Path} path_component
* @param {TubePathStyle} style
* @param {number[]|Float32Array} shape_normal
* @param {number[]|Float32Array} shape
* @param {number[]|Float32Array} shape_transform
*/
function make_geometry_segment(
path_component, style,
shape, shape_normal, shape_transform,
segment_start, segment_end
) {
assert.notNaN(segment_start, 'segment_start');
assert.notNaN(segment_end, 'segment_end');
assert.greaterThanOrEqual(segment_end, segment_start, 'segment_start must be <= segment_end');
const interpolation = path_component.interpolation;
if (interpolation === InterpolationType.Linear) {
return build_geometry_linear(path_component, style, shape, shape_normal, shape_transform, segment_start, segment_end);
} else if (interpolation === InterpolationType.CatmullRom) {
return build_geometry_catmullrom(path_component, style, shape, shape_normal, shape_transform, segment_start, segment_end);
} else {
throw new Error(`Unsupported interpolation type '${interpolation}'`);
}
}
/**
*
* @param {TubePathStyle} style
* @param {AssetManager} am
* @returns {Material|MeshStandardMaterial|MeshMatcapMaterial|MeshBasicMaterial}
*/
function make_material(style, am) {
let material_def;
const material_type = style.material_type;
if (material_type === TubeMaterialType.Basic) {
material_def = new MeshBasicMaterial();
} else if (material_type === TubeMaterialType.Standard) {
material_def = new MeshStandardMaterial({
metalness: style.material.metalness,
roughness: style.material.roughness,
});
} else if (material_type === TubeMaterialType.Matcap) {
material_def = new MeshMatcapMaterial();
am.get({
path: style.material.texture, type: GameAssetType.Texture, callback: asset => {
material_def.matcap = asset.create();
material_def.needsUpdate = true;
}, failure: console.error
});
} else {
throw new Error(`Unsupported material type '${material_type}'`);
}
return material_def;
}
export class TubePathBuilder {
constructor() {
/**
*
* @type {TubePathStyle|null}
*/
this.style = null;
/**
*
* @type {Path|null}
*/
this.path = null;
/**
*
* @type {MaterialManager|null}
*/
this.materials = null;
/**
*
* @type {AssetManager|null}
*/
this.assetManager = null;
}
setPath(path) {
this.path = path;
}
setStyle(style) {
this.style = style;
}
setMaterials(m) {
this.materials = m;
}
setAssetManager(assetManager) {
this.assetManager = assetManager;
}
/**
*
* @param {Entity[]} destination
*/
build(destination) {
const style = this.style;
// generate three.js curve from path
const path_component = this.path;
const pointCount = path_component.getPointCount();
if (pointCount < 2) {
// no curve possible without at least 2 points
return;
}
const material_def = make_material(style, this.assetManager);
material_def.color.set(style.color.toUint());
if (style.opacity < 1) {
material_def.transparent = true;
material_def.opacity = style.opacity;
}
const segment_count = style.path_mask.length / 2;
const shape = fix_shape_normal_order(style.shape);
let shape_normals = style.shape_normals;
if (shape_normals === undefined || shape_normals === null) {
shape_normals = new Float32Array(shape.length);
compute_smooth_profile_normals(shape, shape_normals);
}
const shape_transform = new Float32Array(9);
m3_rm_compose_transform(shape_transform, 0, 0, style.width, style.width, 0, 0, 0);
for (let i = 0; i < segment_count; i++) {
const i2 = i * 2;
let segment_start = style.path_mask[i2];
let segment_end = style.path_mask[i2 + 1];
// check for wrong ordering
if (segment_end < segment_start) {
// violated interval constraint, re-order
const t = segment_end;
segment_end = segment_start;
segment_start = t;
}
const geometry = make_geometry_segment(
path_component, style,
shape, shape_normals, shape_transform,
segment_start, segment_end
);
const entityBuilder = new Entity();
entityBuilder.add(new Transform());
const shaded_geometry = ShadedGeometry.from(geometry, material_def);
shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
entityBuilder.add(shaded_geometry);
destination.push(entityBuilder);
}
}
}