mirror of
https://gitee.com/antv/g6.git
synced 2024-11-29 18:28:19 +08:00
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
This commit is contained in:
parent
bcd080432f
commit
da5297cd8c
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -43,6 +43,7 @@
|
||||
"GSHAPE",
|
||||
"mindmap",
|
||||
"onframe",
|
||||
"Phong",
|
||||
"Polyline",
|
||||
"ranksep"
|
||||
],
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
4
packages/g6-extension-3d/__tests__/demos/index.ts
Normal file
4
packages/g6-extension-3d/__tests__/demos/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './layer-top';
|
||||
export * from './position';
|
||||
export * from './shapes';
|
||||
export * from './solar-system';
|
71
packages/g6-extension-3d/__tests__/demos/layer-top.ts
Normal file
71
packages/g6-extension-3d/__tests__/demos/layer-top.ts
Normal file
@ -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;
|
||||
};
|
44
packages/g6-extension-3d/__tests__/demos/position.ts
Normal file
44
packages/g6-extension-3d/__tests__/demos/position.ts
Normal file
@ -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();
|
||||
};
|
59
packages/g6-extension-3d/__tests__/demos/shapes.ts
Normal file
59
packages/g6-extension-3d/__tests__/demos/shapes.ts
Normal file
@ -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();
|
||||
};
|
112
packages/g6-extension-3d/__tests__/demos/solar-system.ts
Normal file
112
packages/g6-extension-3d/__tests__/demos/solar-system.ts
Normal file
@ -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;
|
||||
};
|
18
packages/g6-extension-3d/__tests__/index.html
Normal file
18
packages/g6-extension-3d/__tests__/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@antv/g6-extension-3d</title>
|
||||
<style>
|
||||
#container {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
43
packages/g6-extension-3d/__tests__/main.ts
Normal file
43
packages/g6-extension-3d/__tests__/main.ts
Normal file
@ -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);
|
||||
}
|
31
packages/g6-extension-3d/__tests__/unit/utils/cache.spec.ts
Normal file
31
packages/g6-extension-3d/__tests__/unit/utils/cache.spec.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
21
packages/g6-extension-3d/__tests__/unit/utils/map.spec.ts
Normal file
21
packages/g6-extension-3d/__tests__/unit/utils/map.spec.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { TupleMap } from '../../../src/utils/map';
|
||||
|
||||
describe('map', () => {
|
||||
it('TupleMap', () => {
|
||||
const map = new TupleMap<Date, string, number>();
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
@ -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));
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
21
packages/g6-extension-3d/jest.config.js
Normal file
21
packages/g6-extension-3d/jest.config.js
Normal file
@ -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'],
|
||||
};
|
@ -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:*"
|
||||
|
@ -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()] : []),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -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<MeshStyleProps> {
|
||||
z?: number;
|
||||
device: Device;
|
||||
material: Material;
|
||||
export type BaseNode3DStyleProps = BaseNodeStyleProps<MeshStyleProps> & {
|
||||
texture?: string | TexImageSource;
|
||||
materialType?: Material['type'];
|
||||
} & PrefixObject<IMaterial, 'material'>;
|
||||
|
||||
export abstract class BaseNode3D<S extends BaseNode3DStyleProps> extends BaseNode<S> {
|
||||
static defaultStyleProps: Partial<BaseNode3DStyleProps> = {
|
||||
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<S>) {
|
||||
super(deepMix({}, { style: BaseNode3D.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
public render(attributes: Required<S>, container: Group) {
|
||||
super.render(attributes, container);
|
||||
}
|
||||
|
||||
protected getKeyStyle(attributes: Required<S>): 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<S>, container: Group = this) {
|
||||
return this.upsert('key', Mesh, this.getKeyStyle(attributes), container);
|
||||
}
|
||||
|
||||
protected abstract getGeometry(attributes: Required<S>): GGeometry<any> | undefined;
|
||||
|
||||
protected getMaterial(attributes: Required<S>): GMaterial<any> | undefined {
|
||||
const { texture } = attributes;
|
||||
const materialStyle = Utils.subStyleProps<Material>(attributes, 'material');
|
||||
return createMaterial(this.plugin, materialStyle, texture);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseNode3D extends BaseNode<BaseNode3DStyleProps> {}
|
||||
|
||||
export interface MeshStyleProps extends BaseStyleProps {
|
||||
x?: number | string;
|
||||
y?: number | string;
|
||||
z?: number | string;
|
||||
geometry: BufferGeometry;
|
||||
material: Material;
|
||||
geometry?: GGeometry<any>;
|
||||
material?: GMaterial<any>;
|
||||
}
|
||||
|
27
packages/g6-extension-3d/src/elements/capsule.ts
Normal file
27
packages/g6-extension-3d/src/elements/capsule.ts
Normal file
@ -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<CapsuleStyleProps> {
|
||||
static defaultStyleProps: Partial<CapsuleStyleProps> = {
|
||||
// radius, height
|
||||
size: [24, 48],
|
||||
heightSegments: 1,
|
||||
sides: 20,
|
||||
};
|
||||
|
||||
constructor(options: DisplayObjectConfig<CapsuleStyleProps>) {
|
||||
super(deepMix({}, { style: Capsule.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getGeometry(attributes: Required<CapsuleStyleProps>): GGeometry<any> | 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 });
|
||||
}
|
||||
}
|
33
packages/g6-extension-3d/src/elements/cone.ts
Normal file
33
packages/g6-extension-3d/src/elements/cone.ts
Normal file
@ -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<ConeStyleProps> {
|
||||
static defaultStyleProps: Partial<ConeStyleProps> = {
|
||||
// baseRadius, peakRadius, height
|
||||
size: [24, 0, 48],
|
||||
heightSegments: 5,
|
||||
capSegments: 20,
|
||||
};
|
||||
|
||||
constructor(options: DisplayObjectConfig<ConeStyleProps>) {
|
||||
super(deepMix({}, { style: Cone.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getGeometry(attributes: Required<ConeStyleProps>): GGeometry<any> | 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 });
|
||||
}
|
||||
}
|
33
packages/g6-extension-3d/src/elements/cube.ts
Normal file
33
packages/g6-extension-3d/src/elements/cube.ts
Normal file
@ -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<CubeStyleProps> {
|
||||
static defaultStyleProps: Partial<CubeStyleProps> = {
|
||||
widthSegments: 1,
|
||||
heightSegments: 1,
|
||||
depthSegments: 1,
|
||||
};
|
||||
|
||||
constructor(options: DisplayObjectConfig<CubeStyleProps>) {
|
||||
super(deepMix({}, { style: Cube.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getGeometry(attributes: Required<CubeStyleProps>): GGeometry<any> | 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 });
|
||||
}
|
||||
}
|
27
packages/g6-extension-3d/src/elements/cylinder.ts
Normal file
27
packages/g6-extension-3d/src/elements/cylinder.ts
Normal file
@ -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<CylinderStyleProps> {
|
||||
static defaultStyleProps: Partial<CylinderStyleProps> = {
|
||||
// radius, height
|
||||
size: [24, 48],
|
||||
heightSegments: 5,
|
||||
capSegments: 20,
|
||||
};
|
||||
|
||||
constructor(options: DisplayObjectConfig<CylinderStyleProps>) {
|
||||
super(deepMix({}, { style: Cylinder.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getGeometry(attributes: Required<CylinderStyleProps>): GGeometry<any> | 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 });
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
26
packages/g6-extension-3d/src/elements/line-3d.ts
Normal file
26
packages/g6-extension-3d/src/elements/line-3d.ts
Normal file
@ -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<Line3DStyleProps>): 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);
|
||||
}
|
||||
}
|
24
packages/g6-extension-3d/src/elements/plane.ts
Normal file
24
packages/g6-extension-3d/src/elements/plane.ts
Normal file
@ -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<PlaneStyleProps> {
|
||||
static defaultStyleProps: Partial<PlaneStyleProps> = {
|
||||
materialCullMode: CullMode.NONE,
|
||||
};
|
||||
|
||||
constructor(options: DisplayObjectConfig<PlaneStyleProps>) {
|
||||
super(deepMix({}, { style: Plane.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getGeometry(attributes: Required<PlaneStyleProps>): GGeometry<any> | undefined {
|
||||
const size = this.getSize();
|
||||
const { width = size[0], depth = size[1], widthSegments, depthSegments } = attributes;
|
||||
return new PlaneGeometry(this.device, { width, depth, widthSegments, depthSegments });
|
||||
}
|
||||
}
|
@ -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<SphereStyleProps>;
|
||||
export class Sphere extends BaseNode3D<SphereStyleProps> {
|
||||
static defaultStyleProps: Partial<SphereStyleProps> = {
|
||||
// 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<SphereStyleProps>) {
|
||||
super(deepMix({}, { style: Sphere.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getKeyStyle(attributes: ParsedSphereStyleProps): MeshStyleProps {
|
||||
return {
|
||||
...super.getKeyStyle(attributes),
|
||||
z: attributes.z || 0,
|
||||
};
|
||||
protected getGeometry(attributes: Required<SphereStyleProps>): GGeometry<any> | undefined {
|
||||
const size = this.getSize();
|
||||
const { radius = size[0] / 2, latitudeBands, longitudeBands } = attributes;
|
||||
return new SphereGeometry(this.device, { radius, latitudeBands, longitudeBands });
|
||||
}
|
||||
}
|
||||
|
27
packages/g6-extension-3d/src/elements/torus.ts
Normal file
27
packages/g6-extension-3d/src/elements/torus.ts
Normal file
@ -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<TorusStyleProps> {
|
||||
static defaultStyleProps: Partial<TorusStyleProps> = {
|
||||
// tubeRadius, ringRadius
|
||||
size: [8, 48],
|
||||
segments: 30,
|
||||
sides: 20,
|
||||
};
|
||||
|
||||
constructor(options: DisplayObjectConfig<TorusStyleProps>) {
|
||||
super(deepMix({}, { style: Torus.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getGeometry(attributes: Required<TorusStyleProps>): GGeometry<any> | 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 });
|
||||
}
|
||||
}
|
16
packages/g6-extension-3d/src/exports.ts
Normal file
16
packages/g6-extension-3d/src/exports.ts
Normal file
@ -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';
|
@ -1,4 +1 @@
|
||||
export { BaseNode3D, Sphere } from './elements';
|
||||
export { renderer } from './renderer';
|
||||
|
||||
export type { BaseNode3DStyleProps, SphereStyleProps } from './elements';
|
||||
export * from './exports';
|
||||
|
3
packages/g6-extension-3d/src/plugins/index.ts
Normal file
3
packages/g6-extension-3d/src/plugins/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { Light } from './light';
|
||||
|
||||
export type { LightOptions } from './light';
|
78
packages/g6-extension-3d/src/plugins/light.ts
Normal file
78
packages/g6-extension-3d/src/plugins/light.ts
Normal file
@ -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 {
|
||||
/**
|
||||
* <zh/> 环境光
|
||||
*
|
||||
* <en/> Ambient light
|
||||
*/
|
||||
ambient?: AmbientLightProps;
|
||||
/**
|
||||
* <zh/> 平行光
|
||||
*
|
||||
* <en/> Directional light
|
||||
*/
|
||||
directional?: DirectionalLightProps;
|
||||
}
|
||||
|
||||
export class Light extends BasePlugin<LightOptions> {
|
||||
static defaultOptions: Partial<LightOptions> = {
|
||||
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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
1
packages/g6-extension-3d/src/types/index.ts
Normal file
1
packages/g6-extension-3d/src/types/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './material';
|
26
packages/g6-extension-3d/src/types/material.ts
Normal file
26
packages/g6-extension-3d/src/types/material.ts
Normal file
@ -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<Omit<IPointMaterial, 'map'>> {
|
||||
type: 'point';
|
||||
map?: string | TexImageSource;
|
||||
}
|
||||
|
||||
interface BasicMaterial extends Partial<Omit<IMeshBasicMaterial, 'map' | 'aoMap'>> {
|
||||
type: 'basic';
|
||||
map?: string | TexImageSource;
|
||||
}
|
||||
|
||||
interface LambertMaterial extends Partial<Omit<IMeshLambertMaterial, 'map' | 'aoMap'>> {
|
||||
type: 'lambert';
|
||||
map?: string | TexImageSource;
|
||||
// aoMap?: string | Texture;
|
||||
}
|
||||
|
||||
interface PhongMaterial extends Partial<Omit<IMeshPhongMaterial, 'map' | 'aoMap'>> {
|
||||
type: 'phong';
|
||||
map?: string | TexImageSource;
|
||||
// aoMap?: string | Texture;
|
||||
}
|
23
packages/g6-extension-3d/src/utils/cache.ts
Normal file
23
packages/g6-extension-3d/src/utils/cache.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* <zh/> 生成对象配置的缓存键
|
||||
*
|
||||
* <en/> Generate cache key of geometry configuration
|
||||
* @param props - <zh/> 对象配置 <en/> geometry configuration
|
||||
* @returns <zh/> 缓存键 <en/> cache key
|
||||
*/
|
||||
export function getCacheKey(props: Record<string, any>): 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);
|
||||
}
|
18
packages/g6-extension-3d/src/utils/map.ts
Normal file
18
packages/g6-extension-3d/src/utils/map.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export class TupleMap<K1, K2, V> {
|
||||
private map = new Map<K1, Map<K2, V>>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
42
packages/g6-extension-3d/src/utils/material.ts
Normal file
42
packages/g6-extension-3d/src/utils/material.ts
Normal file
@ -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<symbol, string | TexImageSource | undefined, GMaterial>();
|
||||
|
||||
const MATERIAL_MAP = {
|
||||
basic: MeshBasicMaterial,
|
||||
point: PointMaterial,
|
||||
lambert: MeshLambertMaterial,
|
||||
phong: MeshPhongMaterial,
|
||||
};
|
||||
|
||||
/**
|
||||
* <zh/> 基于配置创建材质,支持缓存
|
||||
*
|
||||
* <en/> Create material based on configuration, support cache
|
||||
* @param plugin - <zh/> 插件对象 <en/> plugin
|
||||
* @param options - <zh/> 材质配置 <en/> material configuration
|
||||
* @param texture - <zh/> 纹理 <en/> texture
|
||||
* @returns <zh/> 材质对象 <en/> 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;
|
||||
}
|
23
packages/g6-extension-3d/src/utils/texture.ts
Normal file
23
packages/g6-extension-3d/src/utils/texture.ts
Normal file
@ -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<string | TexImageSource, Texture>();
|
||||
|
||||
/**
|
||||
* <zh/> 创建纹理,支持缓存
|
||||
*
|
||||
* <en/> Create texture, support cache
|
||||
* @param plugin - <zh/> 插件对象 <en/> plugin
|
||||
* @param src - <zh/> 纹理路径或者图片对象 <en/> texture path or image object
|
||||
* @returns <zh/> 纹理对象 <en/> 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;
|
||||
}
|
7
packages/g6-extension-3d/tsconfig.build.json
Normal file
7
packages/g6-extension-3d/tsconfig.build.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"extends": "./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__/**/*"]
|
||||
}
|
||||
|
7
packages/g6-extension-3d/tsconfig.test.json
Normal file
7
packages/g6-extension-3d/tsconfig.test.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {}
|
||||
},
|
||||
"include": ["src/**/*", "__tests__/**/*"],
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
16
packages/g6-extension-3d/vite.config.js
Normal file
16
packages/g6-extension-3d/vite.config.js
Normal file
@ -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'),
|
||||
},
|
||||
},
|
||||
});
|
@ -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": {
|
||||
|
@ -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()] : []),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const enum CanvasEvent {
|
||||
export enum CanvasEvent {
|
||||
/** <zh/> 点击时触发 | <en/> Triggered when click */
|
||||
CLICK = 'click',
|
||||
/** <zh/> 双击时触发 | <en/> Triggered when double click */
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const enum CommonEvent {
|
||||
export enum CommonEvent {
|
||||
/** <zh/> 点击时触发 | <en/> Triggered when click */
|
||||
CLICK = 'click',
|
||||
/** <zh/> 双击时触发 | <en/> Triggered when double click */
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const enum ContainerEvent {
|
||||
export enum ContainerEvent {
|
||||
/** <zh/> 按下键盘时触发 | <en/> Triggered when the keyboard is pressed */
|
||||
KEY_DOWN = 'keydown',
|
||||
/** <zh/> 抬起键盘时触发 | <en/> Triggered when the keyboard is lifted */
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const enum EdgeEvent {
|
||||
export enum EdgeEvent {
|
||||
/** <zh/> 点击时触发 | <en/> Triggered when click */
|
||||
CLICK = 'click',
|
||||
/** <zh/> 双击时触发 | <en/> Triggered when double click */
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const enum GraphEvent {
|
||||
export enum GraphEvent {
|
||||
/** <zh/> 视口尺寸变更之前 | <en/> Before the viewport size changes */
|
||||
BEFORE_SIZE_CHANGE = 'beforesizechange',
|
||||
/** <zh/> 视口尺寸变更之后 | <en/> After the viewport size changes */
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const enum NodeEvent {
|
||||
export enum NodeEvent {
|
||||
/** <zh/> 点击时触发 | <en/> Triggered when click */
|
||||
CLICK = 'click',
|
||||
/** <zh/> 双击时触发 | <en/> Triggered when double click */
|
||||
|
@ -107,8 +107,8 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
|
||||
*/
|
||||
public getGraphicStyle<T extends Record<string, any>>(
|
||||
attributes: T,
|
||||
): Omit<T, 'x' | 'y' | 'transform' | 'transformOrigin' | 'className' | 'context' | 'zIndex'> {
|
||||
const { x, y, className, transform, transformOrigin, context, zIndex, ...style } = attributes;
|
||||
): Omit<T, 'x' | 'y' | 'z' | 'transform' | 'transformOrigin' | 'className' | 'context' | 'zIndex'> {
|
||||
const { x, y, z, className, transform, transformOrigin, context, zIndex, ...style } = attributes;
|
||||
return style;
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
Loading…
Reference in New Issue
Block a user