From da5297cd8c81efefb6017c7558af089de440c63d Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 29 Mar 2024 11:57:10 +0800 Subject: [PATCH] feat: add 3d elements, plugin (#5597) * refactor: adjust exports, use enum replace const enum * fix: fix issue that unexpected z attr * feat(utils): add TupleMap and getCacheKey * feat(elements): add 3d elements * feat(plugins): add 3d light * refactor: adjust 3d renderer * refactor: adjust exports * test: add demos * chore: config packages.json, jest config * test: fix test case * chore: update g6-extension-3d version * chore: setup project configs * test: fix demo type issue * chore: config tsconfig for type-check --- .vscode/settings.json | 1 + package.json | 5 +- .../g6-extension-3d/__tests__/demos/index.ts | 4 + .../__tests__/demos/layer-top.ts | 71 +++++++++++ .../__tests__/demos/position.ts | 44 +++++++ .../g6-extension-3d/__tests__/demos/shapes.ts | 59 +++++++++ .../__tests__/demos/solar-system.ts | 112 ++++++++++++++++++ packages/g6-extension-3d/__tests__/index.html | 18 +++ packages/g6-extension-3d/__tests__/main.ts | 43 +++++++ .../__tests__/unit/utils/cache.spec.ts | 31 +++++ .../__tests__/unit/utils/map.spec.ts | 21 ++++ .../__tests__/unit/utils/material.spec.ts | 23 ++++ .../__tests__/unit/utils/texture.spec.ts | 18 +++ packages/g6-extension-3d/jest.config.js | 21 ++++ packages/g6-extension-3d/package.json | 19 +-- packages/g6-extension-3d/rollup.config.mjs | 13 +- .../src/elements/base-node-3d.ts | 71 +++++++++-- .../g6-extension-3d/src/elements/capsule.ts | 27 +++++ packages/g6-extension-3d/src/elements/cone.ts | 33 ++++++ packages/g6-extension-3d/src/elements/cube.ts | 33 ++++++ .../g6-extension-3d/src/elements/cylinder.ts | 27 +++++ .../g6-extension-3d/src/elements/index.ts | 14 +++ .../g6-extension-3d/src/elements/line-3d.ts | 26 ++++ .../g6-extension-3d/src/elements/plane.ts | 24 ++++ .../g6-extension-3d/src/elements/sphere.ts | 32 +++-- .../g6-extension-3d/src/elements/torus.ts | 27 +++++ packages/g6-extension-3d/src/exports.ts | 16 +++ packages/g6-extension-3d/src/index.ts | 5 +- packages/g6-extension-3d/src/plugins/index.ts | 3 + packages/g6-extension-3d/src/plugins/light.ts | 78 ++++++++++++ packages/g6-extension-3d/src/renderer.ts | 14 +++ packages/g6-extension-3d/src/types/index.ts | 1 + .../g6-extension-3d/src/types/material.ts | 26 ++++ packages/g6-extension-3d/src/utils/cache.ts | 23 ++++ packages/g6-extension-3d/src/utils/map.ts | 18 +++ .../g6-extension-3d/src/utils/material.ts | 42 +++++++ packages/g6-extension-3d/src/utils/texture.ts | 23 ++++ packages/g6-extension-3d/tsconfig.build.json | 7 ++ packages/g6-extension-3d/tsconfig.json | 8 +- packages/g6-extension-3d/tsconfig.test.json | 7 ++ packages/g6-extension-3d/vite.config.js | 16 +++ packages/g6/package.json | 5 +- packages/g6/rollup.config.mjs | 13 +- packages/g6/src/constants/events/animation.ts | 2 +- packages/g6/src/constants/events/canvas.ts | 2 +- packages/g6/src/constants/events/common.ts | 2 +- packages/g6/src/constants/events/container.ts | 2 +- packages/g6/src/constants/events/edge.ts | 2 +- packages/g6/src/constants/events/graph.ts | 2 +- packages/g6/src/constants/events/node.ts | 2 +- packages/g6/src/elements/shapes/base-shape.ts | 4 +- packages/g6/src/exports.ts | 8 ++ 52 files changed, 1091 insertions(+), 57 deletions(-) create mode 100644 packages/g6-extension-3d/__tests__/demos/index.ts create mode 100644 packages/g6-extension-3d/__tests__/demos/layer-top.ts create mode 100644 packages/g6-extension-3d/__tests__/demos/position.ts create mode 100644 packages/g6-extension-3d/__tests__/demos/shapes.ts create mode 100644 packages/g6-extension-3d/__tests__/demos/solar-system.ts create mode 100644 packages/g6-extension-3d/__tests__/index.html create mode 100644 packages/g6-extension-3d/__tests__/main.ts create mode 100644 packages/g6-extension-3d/__tests__/unit/utils/cache.spec.ts create mode 100644 packages/g6-extension-3d/__tests__/unit/utils/map.spec.ts create mode 100644 packages/g6-extension-3d/__tests__/unit/utils/material.spec.ts create mode 100644 packages/g6-extension-3d/__tests__/unit/utils/texture.spec.ts create mode 100644 packages/g6-extension-3d/jest.config.js create mode 100644 packages/g6-extension-3d/src/elements/capsule.ts create mode 100644 packages/g6-extension-3d/src/elements/cone.ts create mode 100644 packages/g6-extension-3d/src/elements/cube.ts create mode 100644 packages/g6-extension-3d/src/elements/cylinder.ts create mode 100644 packages/g6-extension-3d/src/elements/line-3d.ts create mode 100644 packages/g6-extension-3d/src/elements/plane.ts create mode 100644 packages/g6-extension-3d/src/elements/torus.ts create mode 100644 packages/g6-extension-3d/src/exports.ts create mode 100644 packages/g6-extension-3d/src/plugins/index.ts create mode 100644 packages/g6-extension-3d/src/plugins/light.ts create mode 100644 packages/g6-extension-3d/src/types/index.ts create mode 100644 packages/g6-extension-3d/src/types/material.ts create mode 100644 packages/g6-extension-3d/src/utils/cache.ts create mode 100644 packages/g6-extension-3d/src/utils/map.ts create mode 100644 packages/g6-extension-3d/src/utils/material.ts create mode 100644 packages/g6-extension-3d/src/utils/texture.ts create mode 100644 packages/g6-extension-3d/tsconfig.build.json create mode 100644 packages/g6-extension-3d/tsconfig.test.json create mode 100644 packages/g6-extension-3d/vite.config.js diff --git a/.vscode/settings.json b/.vscode/settings.json index ee15af195e1..b043fde483d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,6 +43,7 @@ "GSHAPE", "mindmap", "onframe", + "Phong", "Polyline", "ranksep" ], diff --git a/package.json b/package.json index bdd57e4a5df..dabcb6c7951 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jsdom": "^23.2.0", + "lil-gui": "^0.19.2", "limit-size": "^0.1.4", "lint-staged": "^15.2.2", "lodash": "^4.17.21", @@ -54,8 +55,10 @@ "rollup": "^4.12.0", "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-visualizer": "^5.12.0", + "stats.js": "^0.17.0", "ts-jest": "^29.1.2", "turbo": "^1.12.4", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vite": "^5.1.5" } } diff --git a/packages/g6-extension-3d/__tests__/demos/index.ts b/packages/g6-extension-3d/__tests__/demos/index.ts new file mode 100644 index 00000000000..32c6c6cdc2b --- /dev/null +++ b/packages/g6-extension-3d/__tests__/demos/index.ts @@ -0,0 +1,4 @@ +export * from './layer-top'; +export * from './position'; +export * from './shapes'; +export * from './solar-system'; diff --git a/packages/g6-extension-3d/__tests__/demos/layer-top.ts b/packages/g6-extension-3d/__tests__/demos/layer-top.ts new file mode 100644 index 00000000000..158c4d46b09 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/demos/layer-top.ts @@ -0,0 +1,71 @@ +import type { G6Spec, GraphData } from '@antv/g6'; +import { Graph, register } from '@antv/g6'; +import { Light, Line3D, Plane, Sphere, renderer } from '../../src'; + +export const layerTop = async (context: G6Spec) => { + register('plugin', '3d-light', Light); + register('node', 'sphere', Sphere); + register('node', 'plane', Plane); + register('edge', 'line3d', Line3D); + + const result = await fetch('https://assets.antv.antgroup.com/g6/3-layer-top.json'); + const { nodes, edges } = await result.json(); + + const colors = ['rgb(240, 134, 82)', 'rgb(30, 160, 230)', 'rgb(122, 225, 116)']; + const data: GraphData = {}; + data.nodes = nodes.map(({ name, pos, layer }: any) => ({ + id: name, + data: { layer }, + style: { + type: 'sphere', + radius: 10, + color: colors[layer - 1], + materialType: 'phong', + ...pos, + }, + })); + + new Array(3).fill(0).forEach((_, i) => { + data.nodes!.push({ + id: `plane-${i + 1}`, + style: { + type: 'plane', + size: 1000, + color: colors[i], + y: -300 + 300 * i + 10, + }, + }); + }); + + data.edges = edges.map(({ source, target }: any) => ({ + source, + target, + })); + + const graph = new Graph({ + ...context, + renderer, + data, + node: { + style: {}, + }, + edge: { + style: { + type: 'line3d', + lineWidth: 5, + }, + }, + plugins: [ + { + type: '3d-light', + directional: { + direction: [0, 0, 1], + }, + }, + ], + }); + + await graph.render(); + + return graph; +}; diff --git a/packages/g6-extension-3d/__tests__/demos/position.ts b/packages/g6-extension-3d/__tests__/demos/position.ts new file mode 100644 index 00000000000..60b6773bc18 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/demos/position.ts @@ -0,0 +1,44 @@ +import type { G6Spec } from '@antv/g6'; +import { Graph, register } from '@antv/g6'; +import { Light, Line3D, Sphere, renderer } from '../../src'; + +export const positionValidate = async (context: G6Spec) => { + register('plugin', '3d-light', Light); + register('node', 'sphere', Sphere); + register('edge', 'line3d', Line3D); + + const graph = new Graph({ + ...context, + renderer, + data: { + nodes: [ + { id: '1', style: { x: 100, y: 100 } }, + { id: '2', style: { x: 200, y: 200 } }, + { id: '3', style: { x: 200, y: 100, z: 150 } }, + ], + edges: [ + { source: '1', target: '2' }, + { source: '2', target: '3' }, + { source: '1', target: '3' }, + ], + }, + node: { + style: { type: 'sphere', materialType: 'phong' }, + }, + edge: { + style: { + type: 'line3d', + }, + }, + plugins: [ + { + type: '3d-light', + directional: { + direction: [0, 0, 1], + }, + }, + ], + }); + + await graph.render(); +}; diff --git a/packages/g6-extension-3d/__tests__/demos/shapes.ts b/packages/g6-extension-3d/__tests__/demos/shapes.ts new file mode 100644 index 00000000000..fa41509f6d6 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/demos/shapes.ts @@ -0,0 +1,59 @@ +import type { G6Spec } from '@antv/g6'; +import { Graph, register } from '@antv/g6'; +import { Capsule, Cone, Cube, Cylinder, Light, Plane, Sphere, Torus, renderer } from '../../src'; + +export const shapes = async (context: G6Spec) => { + register('plugin', '3d-light', Light); + register('node', 'sphere', Sphere); + register('node', 'plane', Plane); + register('node', 'cylinder', Cylinder); + register('node', 'cone', Cone); + register('node', 'cube', Cube); + register('node', 'capsule', Capsule); + register('node', 'torus', Torus); + + const graph = new Graph({ + ...context, + renderer, + data: { + nodes: [ + { + id: '1', + style: { + type: 'sphere', + texture: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*cdTdTI2bNl8AAAAAAAAAAAAADmJ7AQ/original', + }, + }, + { id: '2', style: { type: 'plane', size: 50 } }, + { id: '3', style: { type: 'cylinder' } }, + { id: '4', style: { type: 'cone' } }, + { + id: '5', + style: { + type: 'cube', + texture: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*8TlCRIsKeUkAAAAAAAAAAAAAARQnAQ', + }, + }, + { id: '6', style: { type: 'capsule' } }, + { id: '7', style: { type: 'torus' } }, + ], + }, + node: { + style: { + materialType: 'phong', + x: (_, i) => 100 + (i % 5) * 100, + y: (_, i) => 100 + Math.floor(i / 5) * 100, + }, + }, + plugins: [ + { + type: '3d-light', + directional: { + direction: [0, 0, 1], + }, + }, + ], + }); + + await graph.render(); +}; diff --git a/packages/g6-extension-3d/__tests__/demos/solar-system.ts b/packages/g6-extension-3d/__tests__/demos/solar-system.ts new file mode 100644 index 00000000000..4f29f615baa --- /dev/null +++ b/packages/g6-extension-3d/__tests__/demos/solar-system.ts @@ -0,0 +1,112 @@ +import type { DisplayObject } from '@antv/g'; +import type { G6Spec, Vector3 } from '@antv/g6'; +import { Graph, register } from '@antv/g6'; +import { Light, Sphere, renderer } from '../../src'; + +export const solarSystem = async (context: G6Spec) => { + register('plugin', '3d-light', Light); + register('node', 'sphere', Sphere); + + const graph = new Graph({ + ...context, + renderer, + background: 'black', + data: { + nodes: [ + { + id: 'sum', + style: { + x: 300, + y: 300, + radius: 100, + texture: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-mZfQr8LtPUAAAAAAAAAAAAADmJ7AQ/original', + }, + }, + { + id: 'mars', + style: { + x: 430, + y: 300, + z: 0, + radius: 20, + texture: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*mniGTZktpecAAAAAAAAAAAAADmJ7AQ/original', + }, + }, + { + id: 'earth', + style: { + x: 500, + y: 300, + z: 0, + radius: 30, + texture: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*cdTdTI2bNl8AAAAAAAAAAAAADmJ7AQ/original', + }, + }, + { + id: 'jupiter', + style: { + x: 600, + y: 300, + z: 0, + radius: 50, + texture: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*t_mQSZYAT70AAAAAAAAAAAAADmJ7AQ/original', + }, + }, + ], + }, + node: { + style: { + type: 'sphere', + materialShininess: 0, + labelText: (d) => d.id, + }, + }, + plugins: [ + { + type: '3d-light', + directional: { + direction: [0, 0, 1], + }, + }, + ], + }); + + await graph.render(); + + // @ts-expect-error graph is private + const element = graph.context.element!; + + const sum = element.getElement('sum')!; + const mars = element.getElement('mars')!; + const earth = element.getElement('earth')!; + const jupiter = element.getElement('jupiter')!; + + const setRotation = (element: DisplayObject, speed: number) => { + setInterval(() => { + element.rotate(0, -speed, 0); + }, 30); + }; + setRotation(sum, 0.1); + setRotation(mars, 0.8); + setRotation(earth, 1); + setRotation(jupiter, 0.5); + + const setRevolution = (element: DisplayObject, center: Vector3, speed: number) => { + setInterval(() => { + const [x, y, z] = element.getPosition(); + const [cx, cy, cz] = center; + const angle = (speed * Math.PI) / 180; + + const newX = (x - cx) * Math.cos(angle) + (z - cz) * Math.sin(angle) + cx; + const newZ = -(x - cx) * Math.sin(angle) + (z - cz) * Math.cos(angle) + cz; + + element.setPosition(newX, y, newZ); + }, 30); + }; + + setRevolution(mars, [300, 300, 0], 1.5); + setRevolution(earth, [300, 300, 0], 1); + setRevolution(jupiter, [300, 300, 0], 0.5); + + return graph; +}; diff --git a/packages/g6-extension-3d/__tests__/index.html b/packages/g6-extension-3d/__tests__/index.html new file mode 100644 index 00000000000..e3003a27952 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/index.html @@ -0,0 +1,18 @@ + + + + + + @antv/g6-extension-3d + + + +
+ + + diff --git a/packages/g6-extension-3d/__tests__/main.ts b/packages/g6-extension-3d/__tests__/main.ts new file mode 100644 index 00000000000..08040144eeb --- /dev/null +++ b/packages/g6-extension-3d/__tests__/main.ts @@ -0,0 +1,43 @@ +import GUI from 'lil-gui'; +import * as demos from './demos'; + +const demoNames = Object.keys(demos); + +const options = { + demo: '', +}; + +const panel = new GUI({ autoPlace: true }); +const __STORAGE__ = '__G6_EXTENSION_3D_DEMO__'; +const load = () => { + const data = localStorage.getItem(__STORAGE__); + if (data) panel.load(JSON.parse(data)); +}; +const save = () => { + localStorage.setItem(__STORAGE__, JSON.stringify(panel.save())); +}; +panel + .add(options, 'demo', demoNames) + .name('Demo') + .onChange((name: string) => { + render(name); + save(); + }); +load(); + +function initContainer() { + const container = document.getElementById('container')!; + container.innerHTML = ''; + return container; +} + +function initContext() { + const container = initContainer(); + return { container }; +} + +function render(name: string) { + const context = initContext(); + const demo = demos[name as keyof typeof demos]; + demo(context); +} diff --git a/packages/g6-extension-3d/__tests__/unit/utils/cache.spec.ts b/packages/g6-extension-3d/__tests__/unit/utils/cache.spec.ts new file mode 100644 index 00000000000..85ea42be590 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/unit/utils/cache.spec.ts @@ -0,0 +1,31 @@ +import { getCacheKey } from '../../../src/utils/cache'; + +describe('cache', () => { + it('getCacheKey plain', () => { + const key = Symbol.for('latitudeBands:16 longitudeBands:16 radius:10'); + + expect( + getCacheKey({ + radius: 10, + latitudeBands: 16, + longitudeBands: 16, + }), + ).toBe(key); + + expect( + getCacheKey({ + longitudeBands: 16, + latitudeBands: 16, + radius: 10, + }), + ).toBe(key); + }); + + it('getCacheKey object', () => { + const object = { a: { b: 1 } }; + + const key1 = getCacheKey(object); + const key2 = getCacheKey(object); + expect(key1).not.toBe(key2); + }); +}); diff --git a/packages/g6-extension-3d/__tests__/unit/utils/map.spec.ts b/packages/g6-extension-3d/__tests__/unit/utils/map.spec.ts new file mode 100644 index 00000000000..fdb6eb3a68c --- /dev/null +++ b/packages/g6-extension-3d/__tests__/unit/utils/map.spec.ts @@ -0,0 +1,21 @@ +import { TupleMap } from '../../../src/utils/map'; + +describe('map', () => { + it('TupleMap', () => { + const map = new TupleMap(); + + const key1 = new Date(); + const key2 = 'key2'; + map.set(key1, key2, 1); + + expect(map.has(key1, key2)).toBe(true); + expect(map.get(key1, key2)).toBe(1); + + map.set(key1, key2, 2); + expect(map.get(key1, key2)).toBe(2); + + const key3 = 'key3'; + expect(map.has(key1, key3)).toBe(false); + expect(map.get(key1, key3)).toBe(undefined); + }); +}); diff --git a/packages/g6-extension-3d/__tests__/unit/utils/material.spec.ts b/packages/g6-extension-3d/__tests__/unit/utils/material.spec.ts new file mode 100644 index 00000000000..ef0f4533bb2 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/unit/utils/material.spec.ts @@ -0,0 +1,23 @@ +import { createMaterial } from '../../../src/utils/material'; + +describe('material', () => { + it('createMaterial', () => { + const plugin: any = { + loadTexture: () => new Object(), + getDevice: () => ({}), + }; + + const materialWithoutTexture = createMaterial(plugin, { type: 'basic' }); + const materialWithTexture = createMaterial(plugin, { type: 'basic' }, 'texture'); + + const image: any = new Object(); + const materialWithImage = createMaterial(plugin, { type: 'basic' }, image); + + expect(materialWithoutTexture).toBe(createMaterial(plugin, { type: 'basic' })); + expect(materialWithTexture).not.toBe(materialWithoutTexture); + expect(materialWithImage).not.toBe(materialWithTexture); + + expect(materialWithTexture).toBe(createMaterial(plugin, { type: 'basic' }, 'texture')); + expect(materialWithImage).toBe(createMaterial(plugin, { type: 'basic' }, image)); + }); +}); diff --git a/packages/g6-extension-3d/__tests__/unit/utils/texture.spec.ts b/packages/g6-extension-3d/__tests__/unit/utils/texture.spec.ts new file mode 100644 index 00000000000..4b38287d632 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/unit/utils/texture.spec.ts @@ -0,0 +1,18 @@ +import { createTexture } from '../../../src/utils/texture'; + +describe('texture', () => { + it('createTexture', () => { + const img1 = 'texture1'; + const img2 = 'texture2'; + + const plugin: any = { + loadTexture: () => new Object(), + }; + + const texture1 = createTexture(plugin, img1); + const texture2 = createTexture(plugin, img2); + + expect(texture1).toBe(createTexture(plugin, img1)); + expect(texture2).not.toBe(texture1); + }); +}); diff --git a/packages/g6-extension-3d/jest.config.js b/packages/g6-extension-3d/jest.config.js new file mode 100644 index 00000000000..6a28540e9d5 --- /dev/null +++ b/packages/g6-extension-3d/jest.config.js @@ -0,0 +1,21 @@ +module.exports = { + preset: 'ts-jest/presets/js-with-ts', + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + diagnostics: { + exclude: ['**'], + }, + tsconfig: { + allowJs: true, + target: 'esnext', + esModuleInterop: true, + }, + }, + ], + }, + testRegex: '(/__tests__/.*\\.(test|spec))\\.(ts|tsx|js)$', + collectCoverageFrom: ['src/**/*.ts'], + moduleFileExtensions: ['ts', 'js', 'json'], +}; diff --git a/packages/g6-extension-3d/package.json b/packages/g6-extension-3d/package.json index cebf7116f85..4d061a9d442 100644 --- a/packages/g6-extension-3d/package.json +++ b/packages/g6-extension-3d/package.json @@ -1,6 +1,6 @@ { - "name": "g6-extension-3d", - "version": "1.0.0", + "name": "@antv/g6-extension-3d", + "version": "0.1.0", "description": "", "keywords": [ "antv", @@ -9,7 +9,7 @@ "3d" ], "license": "MIT", - "author": "", + "author": "Aarebecca", "main": "lib/index.js", "module": "esm/index.js", "types": "lib/index.d.ts", @@ -21,15 +21,15 @@ ], "scripts": { "build": "run-p build:*", - "build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib", - "build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm", - "build:esm:watch": "rimraf ./esm && tsc --module ESNext --outDir esm --watch", + "build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib -p tsconfig.build.json", + "build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm -p tsconfig.build.json", "build:umd": "rimraf ./dist && rollup -c", "ci": "run-s lint type-check build test", + "dev": "vite", "lint": "eslint ./src __tests__ --quiet && prettier ./src __tests__ --check", "prepublishOnly": "npm run ci", "test": "jest", - "type-check": "tsc --noEmit" + "type-check": "tsc --noEmit -p tsconfig.test.json" }, "dependencies": { "@antv/g": "^5.18.25", @@ -37,7 +37,10 @@ "@antv/g-device-api": "^1.6.4", "@antv/g-plugin-3d": "^1.9.34", "@antv/g-plugin-control": "^1.9.22", - "@antv/g-webgl": "^1.9.37" + "@antv/g-plugin-device-renderer": "^2.0.0", + "@antv/g-plugin-dragndrop": "^1.8.22", + "@antv/g-webgl": "^1.9.37", + "@antv/util": "^3.3.7" }, "devDependencies": { "@antv/g6": "workspace:*" diff --git a/packages/g6-extension-3d/rollup.config.mjs b/packages/g6-extension-3d/rollup.config.mjs index 703ec73b50a..0503b9c0165 100644 --- a/packages/g6-extension-3d/rollup.config.mjs +++ b/packages/g6-extension-3d/rollup.config.mjs @@ -7,7 +7,7 @@ import { visualizer } from 'rollup-plugin-visualizer'; const isBundleVis = !!process.env.BUNDLE_VIS; -export default[ +export default [ { input: 'src/index.ts', output: { @@ -16,6 +16,15 @@ export default[ format: 'umd', sourcemap: false, }, - plugins: [nodePolyfills(), resolve(), commonjs(), typescript(), terser(), ...(isBundleVis ? [visualizer()] : [])], + plugins: [ + nodePolyfills(), + resolve(), + commonjs(), + typescript({ + tsconfig: 'tsconfig.build.json', + }), + terser(), + ...(isBundleVis ? [visualizer()] : []), + ], }, ]; diff --git a/packages/g6-extension-3d/src/elements/base-node-3d.ts b/packages/g6-extension-3d/src/elements/base-node-3d.ts index da3b1a8085e..b8cd4d24255 100644 --- a/packages/g6-extension-3d/src/elements/base-node-3d.ts +++ b/packages/g6-extension-3d/src/elements/base-node-3d.ts @@ -1,21 +1,68 @@ -import type { BaseStyleProps } from '@antv/g'; -import { Device } from '@antv/g-device-api'; -import type { BufferGeometry, Material } from '@antv/g-plugin-3d'; +import type { BaseStyleProps, DisplayObjectConfig, Group } from '@antv/g'; +import type { ProceduralGeometry as GGeometry, Material as GMaterial } from '@antv/g-plugin-3d'; +import { Mesh } from '@antv/g-plugin-3d'; +import type { IMaterial, Plugin } from '@antv/g-plugin-device-renderer'; import type { BaseNodeStyleProps } from '@antv/g6'; -import { BaseNode } from '@antv/g6'; +import { BaseNode, Utils } from '@antv/g6'; +import { PrefixObject } from '@antv/g6/lib/types'; +import { deepMix } from '@antv/util'; +import { Material } from '../types'; +import { createMaterial } from '../utils/material'; -export interface BaseNode3DStyleProps extends BaseNodeStyleProps { - z?: number; - device: Device; - material: Material; -} +export type BaseNode3DStyleProps = BaseNodeStyleProps & { + texture?: string | TexImageSource; + materialType?: Material['type']; +} & PrefixObject; + +export abstract class BaseNode3D extends BaseNode { + static defaultStyleProps: Partial = { + materialType: 'basic', + }; + + public type = 'node-3d'; + + protected get plugin() { + const renderer = this.attributes.context!.canvas.renderers['main']; + const plugin = renderer.getPlugin('device-renderer'); + return plugin as unknown as Plugin; + } + + protected get device() { + return this.plugin.getDevice(); + } + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: BaseNode3D.defaultStyleProps }, options)); + } -export abstract class BaseNode3D extends BaseNode {} + public render(attributes: Required, container: Group) { + super.render(attributes, container); + } + + protected getKeyStyle(attributes: Required): MeshStyleProps { + const style = Utils.omitStyleProps(super.getKeyStyle(attributes), 'material'); + const geometry = this.getGeometry(attributes); + const material = this.getMaterial(attributes); + return { x: 0, y: 0, z: 0, ...style, geometry, material }; + } + + protected drawKeyShape(attributes: Required, container: Group = this) { + return this.upsert('key', Mesh, this.getKeyStyle(attributes), container); + } + + protected abstract getGeometry(attributes: Required): GGeometry | undefined; + + protected getMaterial(attributes: Required): GMaterial | undefined { + const { texture } = attributes; + const materialStyle = Utils.subStyleProps(attributes, 'material'); + return createMaterial(this.plugin, materialStyle, texture); + } +} export interface MeshStyleProps extends BaseStyleProps { x?: number | string; y?: number | string; z?: number | string; - geometry: BufferGeometry; - material: Material; + geometry?: GGeometry; + material?: GMaterial; } diff --git a/packages/g6-extension-3d/src/elements/capsule.ts b/packages/g6-extension-3d/src/elements/capsule.ts new file mode 100644 index 00000000000..367ad9cacea --- /dev/null +++ b/packages/g6-extension-3d/src/elements/capsule.ts @@ -0,0 +1,27 @@ +import type { DisplayObjectConfig } from '@antv/g'; +import type { CapsuleGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; +import { CapsuleGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; +import { BaseNode3D } from './base-node-3d'; + +export type CapsuleStyleProps = BaseNode3DStyleProps & CapsuleGeometryProps; + +export class Capsule extends BaseNode3D { + static defaultStyleProps: Partial = { + // radius, height + size: [24, 48], + heightSegments: 1, + sides: 20, + }; + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Capsule.defaultStyleProps }, options)); + } + + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { radius = size[0] / 2, height = size[1], heightSegments, sides } = attributes; + return new CapsuleGeometry(this.device, { radius, height, heightSegments, sides }); + } +} diff --git a/packages/g6-extension-3d/src/elements/cone.ts b/packages/g6-extension-3d/src/elements/cone.ts new file mode 100644 index 00000000000..acd822dffc4 --- /dev/null +++ b/packages/g6-extension-3d/src/elements/cone.ts @@ -0,0 +1,33 @@ +import type { DisplayObjectConfig } from '@antv/g'; +import type { ConeGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; +import { ConeGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; +import { BaseNode3D } from './base-node-3d'; + +export type ConeStyleProps = BaseNode3DStyleProps & ConeGeometryProps; + +export class Cone extends BaseNode3D { + static defaultStyleProps: Partial = { + // baseRadius, peakRadius, height + size: [24, 0, 48], + heightSegments: 5, + capSegments: 20, + }; + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Cone.defaultStyleProps }, options)); + } + + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { + baseRadius = size[0] / 2, + peakRadius = size[1] / 2, + height = size[2], + heightSegments, + capSegments, + } = attributes; + return new ConeGeometry(this.device, { baseRadius, peakRadius, height, heightSegments, capSegments }); + } +} diff --git a/packages/g6-extension-3d/src/elements/cube.ts b/packages/g6-extension-3d/src/elements/cube.ts new file mode 100644 index 00000000000..8b1627e7c71 --- /dev/null +++ b/packages/g6-extension-3d/src/elements/cube.ts @@ -0,0 +1,33 @@ +import type { DisplayObjectConfig } from '@antv/g'; +import type { CubeGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; +import { CubeGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; +import { BaseNode3D } from './base-node-3d'; + +export type CubeStyleProps = BaseNode3DStyleProps & CubeGeometryProps; + +export class Cube extends BaseNode3D { + static defaultStyleProps: Partial = { + widthSegments: 1, + heightSegments: 1, + depthSegments: 1, + }; + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Cube.defaultStyleProps }, options)); + } + + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { + width = size[0], + height = size[1], + depth = size[2], + widthSegments, + heightSegments, + depthSegments, + } = attributes; + return new CubeGeometry(this.device, { width, height, depth, widthSegments, heightSegments, depthSegments }); + } +} diff --git a/packages/g6-extension-3d/src/elements/cylinder.ts b/packages/g6-extension-3d/src/elements/cylinder.ts new file mode 100644 index 00000000000..2249d6f8cb6 --- /dev/null +++ b/packages/g6-extension-3d/src/elements/cylinder.ts @@ -0,0 +1,27 @@ +import type { DisplayObjectConfig } from '@antv/g'; +import type { CylinderGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; +import { CylinderGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; +import { BaseNode3D } from './base-node-3d'; + +export type CylinderStyleProps = BaseNode3DStyleProps & CylinderGeometryProps; + +export class Cylinder extends BaseNode3D { + static defaultStyleProps: Partial = { + // radius, height + size: [24, 48], + heightSegments: 5, + capSegments: 20, + }; + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Cylinder.defaultStyleProps }, options)); + } + + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { radius = size[0] / 2, height = size[1], heightSegments, capSegments } = attributes; + return new CylinderGeometry(this.device, { radius, height, heightSegments, capSegments }); + } +} diff --git a/packages/g6-extension-3d/src/elements/index.ts b/packages/g6-extension-3d/src/elements/index.ts index 640a0b07e45..10f6a67f056 100644 --- a/packages/g6-extension-3d/src/elements/index.ts +++ b/packages/g6-extension-3d/src/elements/index.ts @@ -1,5 +1,19 @@ export { BaseNode3D } from './base-node-3d'; +export { Capsule } from './capsule'; +export { Cone } from './cone'; +export { Cube } from './cube'; +export { Cylinder } from './cylinder'; +export { Line3D } from './line-3d'; +export { Plane } from './plane'; export { Sphere } from './sphere'; +export { Torus } from './torus'; export type { BaseNode3DStyleProps } from './base-node-3d'; +export type { CapsuleStyleProps } from './capsule'; +export type { ConeStyleProps } from './cone'; +export type { CubeStyleProps } from './cube'; +export type { CylinderStyleProps } from './cylinder'; +export type { Line3DStyleProps } from './line-3d'; +export type { PlaneStyleProps } from './plane'; export type { SphereStyleProps } from './sphere'; +export type { TorusStyleProps } from './torus'; diff --git a/packages/g6-extension-3d/src/elements/line-3d.ts b/packages/g6-extension-3d/src/elements/line-3d.ts new file mode 100644 index 00000000000..5ea0f2a2aa5 --- /dev/null +++ b/packages/g6-extension-3d/src/elements/line-3d.ts @@ -0,0 +1,26 @@ +import type { Group } from '@antv/g'; +import { Line } from '@antv/g'; +import type { BaseEdgeStyleProps } from '@antv/g6'; +import { BaseEdge } from '@antv/g6'; + +export interface Line3DStyleProps extends BaseEdgeStyleProps {} + +export class Line3D extends BaseEdge { + protected getKeyPath(): any { + return []; + } + + protected getKeyStyle(attributes: Required): any { + const { sourceNode, targetNode } = attributes; + const [x1, y1, z1] = sourceNode.getPosition(); + const [x2, y2, z2] = targetNode.getPosition(); + + // omit path + const { path, ...style } = super.getKeyStyle(attributes); + return { x1, y1, z1, x2, y2, z2, ...style }; + } + + protected drawKeyShape(attributes = this.parsedAttributes, container: Group = this): any { + return this.upsert('key', Line, this.getKeyStyle(attributes), container); + } +} diff --git a/packages/g6-extension-3d/src/elements/plane.ts b/packages/g6-extension-3d/src/elements/plane.ts new file mode 100644 index 00000000000..23718acad9b --- /dev/null +++ b/packages/g6-extension-3d/src/elements/plane.ts @@ -0,0 +1,24 @@ +import type { DisplayObjectConfig } from '@antv/g'; +import type { ProceduralGeometry as GGeometry, PlaneGeometryProps } from '@antv/g-plugin-3d'; +import { CullMode, PlaneGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; +import { BaseNode3D } from './base-node-3d'; + +export type PlaneStyleProps = BaseNode3DStyleProps & PlaneGeometryProps; + +export class Plane extends BaseNode3D { + static defaultStyleProps: Partial = { + materialCullMode: CullMode.NONE, + }; + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Plane.defaultStyleProps }, options)); + } + + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { width = size[0], depth = size[1], widthSegments, depthSegments } = attributes; + return new PlaneGeometry(this.device, { width, depth, widthSegments, depthSegments }); + } +} diff --git a/packages/g6-extension-3d/src/elements/sphere.ts b/packages/g6-extension-3d/src/elements/sphere.ts index acddc360bef..c86b668d720 100644 --- a/packages/g6-extension-3d/src/elements/sphere.ts +++ b/packages/g6-extension-3d/src/elements/sphere.ts @@ -1,21 +1,27 @@ -import type { Group } from '@antv/g'; -import { Mesh } from '@antv/g-plugin-3d'; -import type { BaseNode3DStyleProps, MeshStyleProps } from './base-node-3d'; +import type { DisplayObjectConfig } from '@antv/g'; +import type { ProceduralGeometry as GGeometry, SphereGeometryProps } from '@antv/g-plugin-3d'; +import { SphereGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; -export interface SphereStyleProps extends BaseNode3DStyleProps {} +export type SphereStyleProps = BaseNode3DStyleProps & SphereGeometryProps; -type ParsedSphereStyleProps = Required; +export class Sphere extends BaseNode3D { + static defaultStyleProps: Partial = { + // radius + size: 24, + latitudeBands: 16, + longitudeBands: 16, + }; -export class Sphere extends BaseNode3D { - protected drawKeyShape(attributes = this.parsedAttributes, container: Group = this) { - return this.upsert('key', Mesh, this.getKeyStyle(attributes), container); + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Sphere.defaultStyleProps }, options)); } - protected getKeyStyle(attributes: ParsedSphereStyleProps): MeshStyleProps { - return { - ...super.getKeyStyle(attributes), - z: attributes.z || 0, - }; + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { radius = size[0] / 2, latitudeBands, longitudeBands } = attributes; + return new SphereGeometry(this.device, { radius, latitudeBands, longitudeBands }); } } diff --git a/packages/g6-extension-3d/src/elements/torus.ts b/packages/g6-extension-3d/src/elements/torus.ts new file mode 100644 index 00000000000..672313d5dbb --- /dev/null +++ b/packages/g6-extension-3d/src/elements/torus.ts @@ -0,0 +1,27 @@ +import type { DisplayObjectConfig } from '@antv/g'; +import type { ProceduralGeometry as GGeometry, TorusGeometryProps } from '@antv/g-plugin-3d'; +import { TorusGeometry } from '@antv/g-plugin-3d'; +import { deepMix } from '@antv/util'; +import type { BaseNode3DStyleProps } from './base-node-3d'; +import { BaseNode3D } from './base-node-3d'; + +export type TorusStyleProps = BaseNode3DStyleProps & TorusGeometryProps; + +export class Torus extends BaseNode3D { + static defaultStyleProps: Partial = { + // tubeRadius, ringRadius + size: [8, 48], + segments: 30, + sides: 20, + }; + + constructor(options: DisplayObjectConfig) { + super(deepMix({}, { style: Torus.defaultStyleProps }, options)); + } + + protected getGeometry(attributes: Required): GGeometry | undefined { + const size = this.getSize(); + const { tubeRadius = size[0] / 2, ringRadius = size[1] / 2, segments, sides } = attributes; + return new TorusGeometry(this.device, { tubeRadius, ringRadius, segments, sides }); + } +} diff --git a/packages/g6-extension-3d/src/exports.ts b/packages/g6-extension-3d/src/exports.ts new file mode 100644 index 00000000000..4f44c7efb2d --- /dev/null +++ b/packages/g6-extension-3d/src/exports.ts @@ -0,0 +1,16 @@ +export { BaseNode3D, Capsule, Cone, Cube, Cylinder, Line3D, Plane, Sphere, Torus } from './elements'; +export { Light } from './plugins'; +export { renderer } from './renderer'; + +export type { + BaseNode3DStyleProps, + CapsuleStyleProps, + ConeStyleProps, + CubeStyleProps, + CylinderStyleProps, + Line3DStyleProps, + PlaneStyleProps, + SphereStyleProps, + TorusStyleProps, +} from './elements'; +export type { LightOptions } from './plugins'; diff --git a/packages/g6-extension-3d/src/index.ts b/packages/g6-extension-3d/src/index.ts index 5d8263af545..8570a426a7e 100644 --- a/packages/g6-extension-3d/src/index.ts +++ b/packages/g6-extension-3d/src/index.ts @@ -1,4 +1 @@ -export { BaseNode3D, Sphere } from './elements'; -export { renderer } from './renderer'; - -export type { BaseNode3DStyleProps, SphereStyleProps } from './elements'; +export * from './exports'; diff --git a/packages/g6-extension-3d/src/plugins/index.ts b/packages/g6-extension-3d/src/plugins/index.ts new file mode 100644 index 00000000000..20242a4f382 --- /dev/null +++ b/packages/g6-extension-3d/src/plugins/index.ts @@ -0,0 +1,3 @@ +export { Light } from './light'; + +export type { LightOptions } from './light'; diff --git a/packages/g6-extension-3d/src/plugins/light.ts b/packages/g6-extension-3d/src/plugins/light.ts new file mode 100644 index 00000000000..76351a0827f --- /dev/null +++ b/packages/g6-extension-3d/src/plugins/light.ts @@ -0,0 +1,78 @@ +import type { AmbientLightProps, DirectionalLightProps } from '@antv/g-plugin-3d'; +import { AmbientLight, DirectionalLight } from '@antv/g-plugin-3d'; +import type { BasePluginOptions, RuntimeContext } from '@antv/g6'; +import { BasePlugin, GraphEvent } from '@antv/g6'; +import { deepMix } from '@antv/util'; + +export interface LightOptions extends BasePluginOptions { + /** + * 环境光 + * + * Ambient light + */ + ambient?: AmbientLightProps; + /** + * 平行光 + * + * Directional light + */ + directional?: DirectionalLightProps; +} + +export class Light extends BasePlugin { + static defaultOptions: Partial = { + ambient: { + fill: '#fff', + intensity: Math.PI * 2, + }, + directional: { + fill: '#fff', + direction: [-1, 0, 1], + intensity: Math.PI * 0.7, + }, + }; + + private ambient?: AmbientLight; + + private directional?: DirectionalLight; + + constructor(context: RuntimeContext, options: LightOptions) { + super(context, deepMix({}, Light.defaultOptions, options)); + this.bindEvents(); + } + + private bindEvents() { + this.context.graph.on(GraphEvent.BEFORE_DRAW, this.setLight); + } + + private unbindEvents() { + this.context.graph.off(GraphEvent.BEFORE_DRAW, this.setLight); + } + + private setLight = () => { + const { ambient, directional } = this.options; + + this.upsertLight('directional', directional); + this.upsertLight('ambient', ambient); + }; + + private upsertLight(type: 'ambient', options?: AmbientLightProps): void; + private upsertLight(type: 'directional', options?: DirectionalLightProps): void; + private upsertLight(type: 'ambient' | 'directional', options?: AmbientLightProps | DirectionalLightProps) { + if (options) { + const light = this[type]; + if (light) light.attr(options); + else { + const Ctor = type === 'ambient' ? AmbientLight : DirectionalLight; + const light = new Ctor({ style: options }); + this[type] = light as any; + this.context.canvas.appendChild(light); + } + } else this[type]?.remove(); + } + + public destroy() { + this.unbindEvents(); + super.destroy(); + } +} diff --git a/packages/g6-extension-3d/src/renderer.ts b/packages/g6-extension-3d/src/renderer.ts index 090cb7e6230..0220d992834 100644 --- a/packages/g6-extension-3d/src/renderer.ts +++ b/packages/g6-extension-3d/src/renderer.ts @@ -1,6 +1,7 @@ import { Renderer as CanvasRenderer } from '@antv/g-canvas'; import { Plugin as Plugin3D } from '@antv/g-plugin-3d'; import { Plugin as PluginControl } from '@antv/g-plugin-control'; +import { Plugin as DragNDropPlugin } from '@antv/g-plugin-dragndrop'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import type { CanvasOptions } from '@antv/g6'; @@ -11,9 +12,22 @@ export const renderer: CanvasOptions['renderer'] = (layer) => { const renderer = new WebGLRenderer(); + if (['main', 'transient'].includes(layer)) { + renderer.registerPlugin( + new DragNDropPlugin({ + isDocumentDraggable: true, + isDocumentDroppable: true, + dragstartDistanceThreshold: 10, + dragstartTimeThreshold: 100, + }), + ); + } + if (layer === 'main') { renderer.registerPlugin(new Plugin3D()); renderer.registerPlugin(new PluginControl()); + } else { + renderer.unregisterPlugin(renderer.getPlugin('dom-interaction')); } return renderer; diff --git a/packages/g6-extension-3d/src/types/index.ts b/packages/g6-extension-3d/src/types/index.ts new file mode 100644 index 00000000000..f6b2aba08f2 --- /dev/null +++ b/packages/g6-extension-3d/src/types/index.ts @@ -0,0 +1 @@ +export * from './material'; diff --git a/packages/g6-extension-3d/src/types/material.ts b/packages/g6-extension-3d/src/types/material.ts new file mode 100644 index 00000000000..7b905e47179 --- /dev/null +++ b/packages/g6-extension-3d/src/types/material.ts @@ -0,0 +1,26 @@ +import type { IPointMaterial } from '@antv/g-plugin-3d'; +import { IMeshBasicMaterial, IMeshLambertMaterial, IMeshPhongMaterial } from '@antv/g-plugin-3d'; + +export type Material = PointMaterial | BasicMaterial | LambertMaterial | PhongMaterial; + +export interface PointMaterial extends Partial> { + type: 'point'; + map?: string | TexImageSource; +} + +interface BasicMaterial extends Partial> { + type: 'basic'; + map?: string | TexImageSource; +} + +interface LambertMaterial extends Partial> { + type: 'lambert'; + map?: string | TexImageSource; + // aoMap?: string | Texture; +} + +interface PhongMaterial extends Partial> { + type: 'phong'; + map?: string | TexImageSource; + // aoMap?: string | Texture; +} diff --git a/packages/g6-extension-3d/src/utils/cache.ts b/packages/g6-extension-3d/src/utils/cache.ts new file mode 100644 index 00000000000..6ff1579bc9e --- /dev/null +++ b/packages/g6-extension-3d/src/utils/cache.ts @@ -0,0 +1,23 @@ +/** + * 生成对象配置的缓存键 + * + * Generate cache key of geometry configuration + * @param props - 对象配置 geometry configuration + * @returns 缓存键 cache key + */ +export function getCacheKey(props: Record): symbol { + const entries = Object.entries(props); + + if (entries.some(([, value]) => typeof value === 'object')) { + return Symbol(); + } + + const str = entries + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([key, value]) => { + return `${key}:${value}`; + }) + .join(' '); + + return Symbol.for(str); +} diff --git a/packages/g6-extension-3d/src/utils/map.ts b/packages/g6-extension-3d/src/utils/map.ts new file mode 100644 index 00000000000..14a10e3e09e --- /dev/null +++ b/packages/g6-extension-3d/src/utils/map.ts @@ -0,0 +1,18 @@ +export class TupleMap { + private map = new Map>(); + + has(key1: K1, key2: K2) { + return this.map.has(key1) && this.map.get(key1)!.has(key2); + } + + get(key1: K1, key2: K2) { + return this.map.get(key1)?.get(key2); + } + + set(key1: K1, key2: K2, value: V) { + if (!this.map.has(key1)) { + this.map.set(key1, new Map()); + } + this.map.get(key1)!.set(key2, value); + } +} diff --git a/packages/g6-extension-3d/src/utils/material.ts b/packages/g6-extension-3d/src/utils/material.ts new file mode 100644 index 00000000000..35d7a32388f --- /dev/null +++ b/packages/g6-extension-3d/src/utils/material.ts @@ -0,0 +1,42 @@ +import type { Material as GMaterial } from '@antv/g-plugin-3d'; +import { MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, PointMaterial } from '@antv/g-plugin-3d'; +import type { Plugin } from '@antv/g-plugin-device-renderer'; +import type { Material } from '../types'; +import { getCacheKey } from './cache'; +import { TupleMap } from './map'; +import { createTexture } from './texture'; + +const MATERIAL_CACHE = new TupleMap(); + +const MATERIAL_MAP = { + basic: MeshBasicMaterial, + point: PointMaterial, + lambert: MeshLambertMaterial, + phong: MeshPhongMaterial, +}; + +/** + * 基于配置创建材质,支持缓存 + * + * Create material based on configuration, support cache + * @param plugin - 插件对象 plugin + * @param options - 材质配置 material configuration + * @param texture - 纹理 texture + * @returns 材质对象 material object + */ +export function createMaterial(plugin: Plugin, options: Material, texture?: string | TexImageSource): GMaterial { + const key = getCacheKey(options); + + if (MATERIAL_CACHE.has(key, texture)) { + return MATERIAL_CACHE.get(key, texture)!; + } + + const device = plugin.getDevice(); + const { type, map = texture, ...opts } = options; + const Ctor = MATERIAL_MAP[type]; + + // @ts-expect-error ignore + const material = new Ctor(device, { map: createTexture(plugin, map), ...opts }); + MATERIAL_CACHE.set(key, texture, material); + return material; +} diff --git a/packages/g6-extension-3d/src/utils/texture.ts b/packages/g6-extension-3d/src/utils/texture.ts new file mode 100644 index 00000000000..095a24a52eb --- /dev/null +++ b/packages/g6-extension-3d/src/utils/texture.ts @@ -0,0 +1,23 @@ +import type { Texture } from '@antv/g-device-api'; +import type { Plugin } from '@antv/g-plugin-device-renderer'; + +const TEXTURE_CACHE = new Map(); + +/** + * 创建纹理,支持缓存 + * + * Create texture, support cache + * @param plugin - 插件对象 plugin + * @param src - 纹理路径或者图片对象 texture path or image object + * @returns 纹理对象 texture object + */ +export function createTexture(plugin: Plugin, src?: string | TexImageSource): Texture | undefined { + if (!src) return; + + if (TEXTURE_CACHE.has(src)) { + return TEXTURE_CACHE.get(src); + } + const texture = plugin.loadTexture(src); + TEXTURE_CACHE.set(src, texture); + return texture; +} diff --git a/packages/g6-extension-3d/tsconfig.build.json b/packages/g6-extension-3d/tsconfig.build.json new file mode 100644 index 00000000000..5790ea55085 --- /dev/null +++ b/packages/g6-extension-3d/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": {} + }, + "include": ["src/**/*"], + "extends": "./tsconfig.json" +} diff --git a/packages/g6-extension-3d/tsconfig.json b/packages/g6-extension-3d/tsconfig.json index 05022cc997b..7dc2201fb3e 100644 --- a/packages/g6-extension-3d/tsconfig.json +++ b/packages/g6-extension-3d/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { - "outDir": "lib" + "strict": true, + "outDir": "lib", + "paths": { + "@antv/g6": ["../g6/src/index.ts"] + } }, "extends": "../../tsconfig.json", - "include": ["src/**/*"] + "include": ["src/**/*", "__tests__/**/*"] } diff --git a/packages/g6-extension-3d/tsconfig.test.json b/packages/g6-extension-3d/tsconfig.test.json new file mode 100644 index 00000000000..74e04566a17 --- /dev/null +++ b/packages/g6-extension-3d/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": {} + }, + "include": ["src/**/*", "__tests__/**/*"], + "extends": "./tsconfig.json" +} diff --git a/packages/g6-extension-3d/vite.config.js b/packages/g6-extension-3d/vite.config.js new file mode 100644 index 00000000000..022782e042c --- /dev/null +++ b/packages/g6-extension-3d/vite.config.js @@ -0,0 +1,16 @@ +import path from 'path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + root: './__tests__', + server: { + port: 8081, + open: '/', + }, + plugins: [{ name: 'isolation' }], + resolve: { + alias: { + '@antv/g6': path.resolve(__dirname, '../g6/src'), + }, + }, +}); diff --git a/packages/g6/package.json b/packages/g6/package.json index dc216e797a1..ff125f746e1 100644 --- a/packages/g6/package.json +++ b/packages/g6/package.json @@ -33,8 +33,8 @@ "scripts": { "build": "run-p build:*", "build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib -p tsconfig.build.json", + "build:dev:watch": "npm run build:esm -- --watch", "build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm -p tsconfig.build.json", - "build:esm:watch": "rimraf ./esm && tsc --module ESNext --outDir esm --watch -p tsconfig.build.json", "build:umd": "rimraf ./dist && rollup -c && npm run size", "bundle-vis": "cross-env BUNDLE_VIS=1 npm run build:umd", "ci": "run-s lint type-check build test", @@ -75,9 +75,6 @@ "@types/xmlserializer": "^0.6.6", "jest-canvas-mock": "^2.5.1", "jest-random-mock": "^1.0.0", - "lil-gui": "^0.19.2", - "stats.js": "^0.17.0", - "vite": "^5.1.5", "xmlserializer": "^0.6.1" }, "publishConfig": { diff --git a/packages/g6/rollup.config.mjs b/packages/g6/rollup.config.mjs index b09d88d8538..eb8fce655a0 100644 --- a/packages/g6/rollup.config.mjs +++ b/packages/g6/rollup.config.mjs @@ -7,7 +7,7 @@ import { visualizer } from 'rollup-plugin-visualizer'; const isBundleVis = !!process.env.BUNDLE_VIS; -export default[ +export default [ { input: 'src/index.ts', output: { @@ -16,6 +16,15 @@ export default[ format: 'umd', sourcemap: false, }, - plugins: [nodePolyfills(), resolve(), commonjs(), typescript(), terser(), ...(isBundleVis ? [visualizer()] : [])], + plugins: [ + nodePolyfills(), + resolve(), + commonjs(), + typescript({ + tsconfig: 'tsconfig.build.json', + }), + terser(), + ...(isBundleVis ? [visualizer()] : []), + ], }, ]; diff --git a/packages/g6/src/constants/events/animation.ts b/packages/g6/src/constants/events/animation.ts index 80b10e7d09b..4c0c01cfc87 100644 --- a/packages/g6/src/constants/events/animation.ts +++ b/packages/g6/src/constants/events/animation.ts @@ -1,4 +1,4 @@ -export const enum AnimationType { +export enum AnimationType { DRAW = 'draw', TRANSFORM = 'transform', // LAYOUT = 'layout', // 布局没有统一的动画对象,因此不抛出动画事件 | There is no unified animation object for layout, so no animation event is thrown diff --git a/packages/g6/src/constants/events/canvas.ts b/packages/g6/src/constants/events/canvas.ts index 7e72f4ea913..b1dfcddae14 100644 --- a/packages/g6/src/constants/events/canvas.ts +++ b/packages/g6/src/constants/events/canvas.ts @@ -1,4 +1,4 @@ -export const enum CanvasEvent { +export enum CanvasEvent { /** 点击时触发 | Triggered when click */ CLICK = 'click', /** 双击时触发 | Triggered when double click */ diff --git a/packages/g6/src/constants/events/common.ts b/packages/g6/src/constants/events/common.ts index d413882f0d0..bf50608107e 100644 --- a/packages/g6/src/constants/events/common.ts +++ b/packages/g6/src/constants/events/common.ts @@ -1,4 +1,4 @@ -export const enum CommonEvent { +export enum CommonEvent { /** 点击时触发 | Triggered when click */ CLICK = 'click', /** 双击时触发 | Triggered when double click */ diff --git a/packages/g6/src/constants/events/container.ts b/packages/g6/src/constants/events/container.ts index d2302c20210..99467d3d521 100644 --- a/packages/g6/src/constants/events/container.ts +++ b/packages/g6/src/constants/events/container.ts @@ -1,4 +1,4 @@ -export const enum ContainerEvent { +export enum ContainerEvent { /** 按下键盘时触发 | Triggered when the keyboard is pressed */ KEY_DOWN = 'keydown', /** 抬起键盘时触发 | Triggered when the keyboard is lifted */ diff --git a/packages/g6/src/constants/events/edge.ts b/packages/g6/src/constants/events/edge.ts index 55f5efe7abf..b06bcb8c566 100644 --- a/packages/g6/src/constants/events/edge.ts +++ b/packages/g6/src/constants/events/edge.ts @@ -1,4 +1,4 @@ -export const enum EdgeEvent { +export enum EdgeEvent { /** 点击时触发 | Triggered when click */ CLICK = 'click', /** 双击时触发 | Triggered when double click */ diff --git a/packages/g6/src/constants/events/graph.ts b/packages/g6/src/constants/events/graph.ts index 6ddb607ef18..3d4b3868402 100644 --- a/packages/g6/src/constants/events/graph.ts +++ b/packages/g6/src/constants/events/graph.ts @@ -1,4 +1,4 @@ -export const enum GraphEvent { +export enum GraphEvent { /** 视口尺寸变更之前 | Before the viewport size changes */ BEFORE_SIZE_CHANGE = 'beforesizechange', /** 视口尺寸变更之后 | After the viewport size changes */ diff --git a/packages/g6/src/constants/events/node.ts b/packages/g6/src/constants/events/node.ts index 342df032888..9073f974c1d 100644 --- a/packages/g6/src/constants/events/node.ts +++ b/packages/g6/src/constants/events/node.ts @@ -1,4 +1,4 @@ -export const enum NodeEvent { +export enum NodeEvent { /** 点击时触发 | Triggered when click */ CLICK = 'click', /** 双击时触发 | Triggered when double click */ diff --git a/packages/g6/src/elements/shapes/base-shape.ts b/packages/g6/src/elements/shapes/base-shape.ts index 60d5d23a567..6358e2ea10c 100644 --- a/packages/g6/src/elements/shapes/base-shape.ts +++ b/packages/g6/src/elements/shapes/base-shape.ts @@ -107,8 +107,8 @@ export abstract class BaseShape extends */ public getGraphicStyle>( attributes: T, - ): Omit { - const { x, y, className, transform, transformOrigin, context, zIndex, ...style } = attributes; + ): Omit { + const { x, y, z, className, transform, transformOrigin, context, zIndex, ...style } = attributes; return style; } diff --git a/packages/g6/src/exports.ts b/packages/g6/src/exports.ts index 99ef33f2817..1016cf0fbaf 100644 --- a/packages/g6/src/exports.ts +++ b/packages/g6/src/exports.ts @@ -1,5 +1,6 @@ import { dark, light } from './themes'; import { idOf } from './utils/id'; +import { omitStyleProps, subStyleProps } from './utils/prefix'; import { treeToGraphData } from './utils/tree'; export { BaseBehavior } from './behaviors'; @@ -7,6 +8,8 @@ export { CanvasEvent, ComboEvent, CommonEvent, ContainerEvent, EdgeEvent, GraphE export { BaseCombo } from './elements/combos'; export { BaseEdge } from './elements/edges'; export { BaseNode } from './elements/nodes'; +export { BaseShape } from './elements/shapes'; +export { BasePlugin } from './plugins'; export { getExtension, getExtensions, register } from './registry'; export { Graph } from './runtime/graph'; @@ -14,6 +17,9 @@ export type { BaseBehaviorOptions } from './behaviors'; export type { BaseComboStyleProps } from './elements/combos'; export type { BaseEdgeStyleProps } from './elements/edges'; export type { BaseNodeStyleProps } from './elements/nodes'; +export type { BaseShapeStyleProps } from './elements/shapes'; +export type { BasePluginOptions } from './plugins'; +export type { RuntimeContext } from './runtime/types'; export type { BehaviorOptions, CanvasOptions, @@ -35,6 +41,8 @@ export type { Point, Vector2, Vector3 } from './types'; const Utils = { idOf, treeToGraphData, + subStyleProps, + omitStyleProps, }; const Theme = {