UNPKG

polygonjs-engine

Version:

node-based webgl 3D engine https://polygonjs.com

137 lines (124 loc) 4.05 kB
/** * Creates a heightmap * * */ import {DataTexture} from 'three/src/textures/DataTexture'; import {TypedSopNode} from './_Base'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {NodeContext} from '../../poly/NodeContext'; import {CoreGroup} from '../../../core/geometry/Group'; import {BaseCopNodeType} from '../cop/_Base'; import {InputCloneMode} from '../../poly/InputCloneMode'; import {CoreObject} from '../../../core/geometry/Object'; import {Texture} from 'three/src/textures/Texture'; import {CoreImage} from '../../../core/Image'; import {NODE_PATH_DEFAULT} from '../../../core/Walker'; class HeightMapSopParamsConfig extends NodeParamsConfig { /** @param texture node to load the heightmap from */ texture = ParamConfig.OPERATOR_PATH(NODE_PATH_DEFAULT.NODE.UV, { nodeSelection: {context: NodeContext.COP}, }); /** @param values multiplier */ mult = ParamConfig.FLOAT(1); } const ParamsConfig = new HeightMapSopParamsConfig(); export class HeightMapSopNode extends TypedSopNode<HeightMapSopParamsConfig> { params_config = ParamsConfig; static type() { return 'heightMap'; } initializeNode() { this.io.inputs.setCount(1); this.io.inputs.initInputsClonedState(InputCloneMode.FROM_NODE); } async cook(input_contents: CoreGroup[]) { const core_group = input_contents[0]; const node = this.p.texture.found_node(); if (node) { const node_context = node.nodeContext(); if (node_context == NodeContext.COP) { const texture_node = node as BaseCopNodeType; const container = await texture_node.requestContainer(); const texture = container.texture(); for (let core_object of core_group.coreObjects()) { this._set_position_from_data_texture(core_object, texture); } } else { this.states.error.set('found node is not a texture'); } } core_group.computeVertexNormals(); this.setCoreGroup(core_group); } private _set_position_from_data_texture(core_object: CoreObject, texture: Texture) { const texture_data = this._data_from_texture(texture); if (!texture_data) { return; } const {data, resx, resy} = texture_data; const texture_component_size = data.length / (resx * resy); const geometry = core_object.coreGeometry()?.geometry(); if (!geometry) { return; } const positions = geometry.getAttribute('position').array as number[]; const uv_attrib = geometry.getAttribute('uv'); const normal_attrib = geometry.getAttribute('normal'); if (uv_attrib == null) { this.states.error.set('uvs are required'); return; } if (normal_attrib == null) { this.states.error.set('normals are required'); return; } const uvs = uv_attrib.array; const normals = normal_attrib.array; const points_count = positions.length / 3; let uv_stride, uvx, uvy, x, y, j, val; let index: number = 0; for (let i = 0; i < points_count; i++) { uv_stride = i * 2; uvx = uvs[uv_stride]; uvy = uvs[uv_stride + 1]; x = Math.floor((resx - 1) * uvx); y = Math.floor((resy - 1) * (1 - uvy)); j = y * resx + x; val = data[texture_component_size * j]; index = i * 3; positions[index + 0] += normals[index + 0] * val * this.pv.mult; positions[index + 1] += normals[index + 1] * val * this.pv.mult; positions[index + 2] += normals[index + 2] * val * this.pv.mult; } } private _data_from_texture(texture: Texture) { if (texture.image) { if (texture.image.data) { return this._data_from_data_texture(texture as DataTexture); } return this._data_from_default_texture(texture); } } private _data_from_default_texture(texture: Texture) { const resx = texture.image.width; const resy = texture.image.height; const image_data = CoreImage.data_from_image(texture.image); const data = image_data.data; return { data, resx, resy, }; } private _data_from_data_texture(texture: DataTexture) { const data = texture.image.data; const resx = texture.image.width; const resy = texture.image.height; return { data, resx, resy, }; } }