finished the shadows
This commit is contained in:
parent
fc9833d830
commit
92937d16ce
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -5609,11 +5609,6 @@
|
||||||
"invert-kv": "^1.0.0"
|
"invert-kv": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"line-intersect": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/line-intersect/-/line-intersect-2.2.1.tgz",
|
|
||||||
"integrity": "sha512-uOPErCqtEnHYsnesl56XmKm9nWF27kOqZCuibJEkyAJ23FHsKmOHo8FPT6SRAp2h3wzvyXJNjbPsq9FF5x29vw=="
|
|
||||||
},
|
|
||||||
"load-json-file": {
|
"load-json-file": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eix-js/utils": "0.0.6",
|
"@eix-js/utils": "0.0.6",
|
||||||
"deepmerge": "^4.0.0",
|
"deepmerge": "^4.0.0",
|
||||||
"line-intersect": "^2.2.1",
|
|
||||||
"mainloop.js": "^1.0.4",
|
"mainloop.js": "^1.0.4",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
import { Transform, vector2 } from './Transform'
|
import { Transform, vector2 } from './Transform'
|
||||||
import { Screen } from '../../core/classes/Screen'
|
import { Screen } from '../../core/classes/Screen'
|
||||||
|
import { relativeTo } from '../../vector2/helpers/basic'
|
||||||
|
|
||||||
export class Camera {
|
export class Camera {
|
||||||
private screen = new Screen()
|
private screen = new Screen()
|
||||||
public transform = new Transform([0, 0], [this.screen.x, this.screen.y])
|
public transform = new Transform([0, 0], [this.screen.x, this.screen.y])
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.screen.height.subscribe(value => {
|
||||||
|
this.transform.height = value
|
||||||
|
})
|
||||||
|
this.screen.width.subscribe(value => {
|
||||||
|
this.transform.width = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public toWordPostition(position: vector2) {
|
public toWordPostition(position: vector2) {
|
||||||
return position.map(
|
return relativeTo(this.transform.position, position)
|
||||||
(value, index) => value + this.transform.position[index]
|
|
||||||
) as vector2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,10 @@ export class MouseManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDirection() {
|
public getDirection() {
|
||||||
return clamp(-1, 1, this.total / this.history.length)
|
if (this.history.length) {
|
||||||
|
return clamp(-1, 1, this.total / this.history.length)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(maybeAgain = true) {
|
public update(maybeAgain = true) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { renderGate } from '../helpers/renderGate'
|
||||||
import { renderGateShadow } from '../helpers/renderGateShadow'
|
import { renderGateShadow } from '../helpers/renderGateShadow'
|
||||||
import { MouseManager } from './MouseManager'
|
import { MouseManager } from './MouseManager'
|
||||||
import { Screen } from '../../core/classes/Screen'
|
import { Screen } from '../../core/classes/Screen'
|
||||||
|
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
||||||
|
|
||||||
export interface SimulationRendererOptions {
|
export interface SimulationRendererOptions {
|
||||||
shadows: {
|
shadows: {
|
||||||
|
@ -27,7 +28,7 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
color: 'rgba(0,0,0,0.3)',
|
color: 'rgba(0,0,0,0.3)',
|
||||||
gateHeight: 10,
|
gateHeight: 10,
|
||||||
lightHeight: 50
|
lightHeight: 100
|
||||||
},
|
},
|
||||||
dnd: {
|
dnd: {
|
||||||
rotation: Math.PI / 12 // 7.5 degrees
|
rotation: Math.PI / 12 // 7.5 degrees
|
||||||
|
@ -41,9 +42,13 @@ export class SimulationRenderer {
|
||||||
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
||||||
|
|
||||||
public selectedGate: number | null = null
|
public selectedGate: number | null = null
|
||||||
public selectOffset: vector2 = [0, 0]
|
public lastMousePosition: vector2 = [0, 0]
|
||||||
public movedSelection = false
|
public movedSelection = false
|
||||||
|
|
||||||
|
// first bit = dragging
|
||||||
|
// second bit = moving around
|
||||||
|
private mouseState = 0b00
|
||||||
|
|
||||||
private options: SimulationRendererOptions
|
private options: SimulationRendererOptions
|
||||||
private mouseManager = new MouseManager(this.mouseMoveOutput)
|
private mouseManager = new MouseManager(this.mouseMoveOutput)
|
||||||
private screen = new Screen()
|
private screen = new Screen()
|
||||||
|
@ -71,10 +76,11 @@ export class SimulationRenderer {
|
||||||
if (pointInSquare(worldPosition, transform)) {
|
if (pointInSquare(worldPosition, transform)) {
|
||||||
this.mouseManager.clear(worldPosition[0])
|
this.mouseManager.clear(worldPosition[0])
|
||||||
|
|
||||||
|
this.mouseState |= 1
|
||||||
this.movedSelection = false
|
this.movedSelection = false
|
||||||
|
|
||||||
this.selectedGate = id
|
this.selectedGate = id
|
||||||
this.selectOffset = worldPosition.map(
|
this.lastMousePosition = worldPosition.map(
|
||||||
(position, index) =>
|
(position, index) =>
|
||||||
position - transform.position[index]
|
position - transform.position[index]
|
||||||
) as vector2
|
) as vector2
|
||||||
|
@ -84,8 +90,13 @@ export class SimulationRenderer {
|
||||||
if (gateNode) {
|
if (gateNode) {
|
||||||
return this.simulation.gates.moveOnTop(gateNode)
|
return this.simulation.gates.moveOnTop(gateNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lastMousePosition = worldPosition
|
||||||
|
this.mouseState |= 2
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseUpOutput.subscribe(event => {
|
this.mouseUpOutput.subscribe(event => {
|
||||||
|
@ -97,34 +108,56 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedGate = null
|
this.selectedGate = null
|
||||||
|
this.mouseState &= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.mouseState &= 0b00
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseMoveOutput.subscribe(event => {
|
this.mouseMoveOutput.subscribe(event => {
|
||||||
if (this.selectedGate !== null) {
|
const worldPosition = this.camera.toWordPostition(event.position)
|
||||||
|
|
||||||
|
if (this.mouseState & 1 && this.selectedGate !== null) {
|
||||||
const gate = this.getGateById(this.selectedGate)
|
const gate = this.getGateById(this.selectedGate)
|
||||||
|
|
||||||
if (!gate || !gate.data) return
|
if (!gate || !gate.data) return
|
||||||
|
|
||||||
const transform = gate.data.transform
|
const transform = gate.data.transform
|
||||||
const worldPosition = this.camera.toWordPostition(
|
|
||||||
event.position
|
|
||||||
)
|
|
||||||
|
|
||||||
transform.x = worldPosition[0] - this.selectOffset[0]
|
transform.x = worldPosition[0] - this.lastMousePosition[0]
|
||||||
transform.y = worldPosition[1] - this.selectOffset[1]
|
transform.y = worldPosition[1] - this.lastMousePosition[1]
|
||||||
|
|
||||||
if (!this.movedSelection) {
|
if (!this.movedSelection) {
|
||||||
this.movedSelection = true
|
this.movedSelection = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((this.mouseState >> 1) & 1) {
|
||||||
|
const offset = invert(
|
||||||
|
relativeTo(this.lastMousePosition, worldPosition)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.camera.transform.position = add(
|
||||||
|
this.camera.transform.position,
|
||||||
|
invert(offset)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.lastMousePosition = this.camera.toWordPostition(
|
||||||
|
event.position
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(ctx: CanvasRenderingContext2D) {
|
public render(ctx: CanvasRenderingContext2D) {
|
||||||
this.clear(ctx)
|
this.clear(ctx)
|
||||||
|
|
||||||
const center = this.screen.center
|
ctx.translate(...this.camera.transform.position)
|
||||||
|
|
||||||
|
const center = relativeTo(
|
||||||
|
this.camera.transform.position,
|
||||||
|
this.screen.center
|
||||||
|
)
|
||||||
|
|
||||||
// render gates
|
// render gates
|
||||||
for (const gate of this.simulation.gates) {
|
for (const gate of this.simulation.gates) {
|
||||||
|
@ -140,11 +173,12 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
renderGate(ctx, gate)
|
renderGate(ctx, gate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.translate(...invert(this.camera.transform.position))
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear(ctx: CanvasRenderingContext2D) {
|
public clear(ctx: CanvasRenderingContext2D) {
|
||||||
const boundingBox = this.camera.transform.getBoundingBox()
|
ctx.clearRect(0, 0, ...this.camera.transform.scale)
|
||||||
ctx.clearRect(...boundingBox)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGateById(id: number) {
|
public getGateById(id: number) {
|
||||||
|
@ -159,6 +193,9 @@ export class SimulationRenderer {
|
||||||
selected.transform.rotation =
|
selected.transform.rotation =
|
||||||
this.mouseManager.getDirection() * this.options.dnd.rotation
|
this.mouseManager.getDirection() * this.options.dnd.rotation
|
||||||
} else {
|
} else {
|
||||||
|
if (selected) {
|
||||||
|
selected.transform.rotation = 0
|
||||||
|
}
|
||||||
this.mouseManager.update()
|
this.mouseManager.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { BehaviorSubject } from 'rxjs'
|
|
||||||
import { allCombinations } from '../helpers/allCombinations'
|
import { allCombinations } from '../helpers/allCombinations'
|
||||||
import { rotateAroundVector } from '../../vector2/helpers/rotate'
|
import { rotateAroundVector } from '../../vector2/helpers/rotate'
|
||||||
|
|
||||||
|
@ -98,4 +97,12 @@ export class Transform {
|
||||||
set y(value: number) {
|
set y(value: number) {
|
||||||
this.position = [this.x, value]
|
this.position = [this.x, value]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set width(value: number) {
|
||||||
|
this.scale = [value, this.height]
|
||||||
|
}
|
||||||
|
|
||||||
|
set height(value: number) {
|
||||||
|
this.scale = [this.width, value]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,7 @@
|
||||||
import { Gate } from '../classes/Gate'
|
import { Gate } from '../classes/Gate'
|
||||||
import { projectPointOnPlane } from './projectPoint'
|
import { projectPointOnPlane } from './projectPoint'
|
||||||
import { drawPolygon } from './drawPolygon'
|
import { drawPolygon } from './drawPolygon'
|
||||||
import { vector3, vector2, vector4, vector8 } from '../classes/Transform'
|
import { vector3 } from '../classes/Transform'
|
||||||
import { checkIntersection } from 'line-intersect'
|
|
||||||
import { reverseArray } from './reverseArray'
|
|
||||||
import { minVector, maxVector } from '../../vector2/helpers/minmaxVector'
|
|
||||||
import { relativeTo } from '../../vector2/helpers/basic'
|
|
||||||
|
|
||||||
export const pointRecivesLight = (
|
|
||||||
points: vector2[], //this needs to have an even length
|
|
||||||
light: vector3,
|
|
||||||
index: number
|
|
||||||
) => {
|
|
||||||
const point = points[index]
|
|
||||||
const oposittePoint = points[(index + points.length / 2) % points.length]
|
|
||||||
|
|
||||||
const edgesToCheck = [
|
|
||||||
[oposittePoint, points[(index + 1) % points.length]],
|
|
||||||
[oposittePoint, points[index === 0 ? points.length - 1 : index - 1]]
|
|
||||||
].map(points => points.flat() as vector4)
|
|
||||||
|
|
||||||
for (const edge of edgesToCheck) {
|
|
||||||
const intersectionCheckParameters = [
|
|
||||||
[light[0], light[1]],
|
|
||||||
point,
|
|
||||||
edge
|
|
||||||
].flat() as vector8
|
|
||||||
|
|
||||||
const result = checkIntersection(...intersectionCheckParameters).type
|
|
||||||
|
|
||||||
if (result === 'intersecting') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
export const renderGateShadow = (
|
export const renderGateShadow = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -47,66 +13,9 @@ export const renderGateShadow = (
|
||||||
ctx.fillStyle = color
|
ctx.fillStyle = color
|
||||||
|
|
||||||
const points = gate.transform.getPoints()
|
const points = gate.transform.getPoints()
|
||||||
const exposedPoints = points.filter((point, index) =>
|
const projections = points.map(point =>
|
||||||
pointRecivesLight(points, light, index)
|
|
||||||
)
|
|
||||||
|
|
||||||
let includedPoints = [...points]
|
|
||||||
|
|
||||||
if (exposedPoints.length === 4) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exposedPoints.length === 3) {
|
|
||||||
const minimum = minVector(
|
|
||||||
...exposedPoints.map(point =>
|
|
||||||
relativeTo(point, light.slice(0, 2) as vector2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
includedPoints.splice(includedPoints.indexOf(exposedPoints[minimum]), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includedPoints.length === 3) {
|
|
||||||
const maximum = maxVector(
|
|
||||||
...includedPoints.map(point =>
|
|
||||||
relativeTo(point, light.slice(0, 2) as vector2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const otherIndices = [(maximum + 1) % 3, (maximum + 2) % 3]
|
|
||||||
const newIndices = [otherIndices[0], maximum, otherIndices[1]]
|
|
||||||
|
|
||||||
includedPoints = newIndices.map(index => includedPoints[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
let projections = includedPoints.map(point =>
|
|
||||||
// ts doesnt let me do [...point, gateHeight]
|
|
||||||
projectPointOnPlane([point[0], point[1], gateHeight], light)
|
projectPointOnPlane([point[0], point[1], gateHeight], light)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (exposedPoints.length === 2) {
|
drawPolygon(ctx, projections)
|
||||||
const toProject = includedPoints.filter(
|
|
||||||
point => exposedPoints.indexOf(point) === -1
|
|
||||||
)
|
|
||||||
|
|
||||||
projections = toProject.map(point =>
|
|
||||||
projectPointOnPlane([point[0], point[1], gateHeight], light)
|
|
||||||
)
|
|
||||||
|
|
||||||
includedPoints = reverseArray(exposedPoints)
|
|
||||||
|
|
||||||
const firstIncludedIndex = points.indexOf(toProject[0])
|
|
||||||
const firstExposedPointIndex = points.indexOf(includedPoints[0])
|
|
||||||
|
|
||||||
if ((firstIncludedIndex + 2) % 4 === firstExposedPointIndex % 4) {
|
|
||||||
const temporary = includedPoints[0]
|
|
||||||
includedPoints[0] = includedPoints[1]
|
|
||||||
includedPoints[1] = temporary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const polygon = [includedPoints, reverseArray(projections)].flat()
|
|
||||||
|
|
||||||
drawPolygon(ctx, polygon)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": { "*": ["types/*"] },
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
|
13
types/line-intersect.d.ts
vendored
13
types/line-intersect.d.ts
vendored
|
@ -1,13 +0,0 @@
|
||||||
export const checkIntersection: (
|
|
||||||
x1: number,
|
|
||||||
y1: number,
|
|
||||||
x2: number,
|
|
||||||
y2: number,
|
|
||||||
x3: number,
|
|
||||||
y3: number,
|
|
||||||
x4: number,
|
|
||||||
y4: number
|
|
||||||
) => {
|
|
||||||
type: 'colinear' | 'parallel' | 'none' | 'intersecting'
|
|
||||||
}
|
|
||||||
export const colinearPointWithinSegment: any
|
|
Loading…
Reference in a new issue